* Some UI rework
* Start removing (retry) logic from ruffyscripter to ComboPlugin
* Cleanups all over
* Remove TDD stuff, this can be done independently of a pump
* New SPI/API methods confirmAlert, readReservoirLevelAndLastBolus
* Add warning and error codes from Combo manual
* Rework commands to just execute an action (verification will be in
  ComboPlugin eventually, together with retry logic)
* Rework commands to update state in field as command progresses
  rather than returnin/throwing.
* Initial version reading bolus and error history (no DB sync yet).
This commit is contained in:
Johannes Mockenhaupt 2017-10-29 11:36:35 +01:00
parent 14307cb77b
commit 6b6d252173
No known key found for this signature in database
GPG key ID: 9E1EA6AF7BBBB0D1
34 changed files with 909 additions and 1121 deletions

View file

@ -15,8 +15,6 @@ import com.squareup.otto.Subscribe;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.List;
import de.jotomo.ruffy.spi.CommandResult; import de.jotomo.ruffy.spi.CommandResult;
import de.jotomo.ruffy.spi.PumpState; import de.jotomo.ruffy.spi.PumpState;
import de.jotomo.ruffy.spi.history.Bolus; import de.jotomo.ruffy.spi.history.Bolus;
@ -62,20 +60,17 @@ public class ComboFragment extends SubscriberFragment implements View.OnClickLis
public void onClick(View view) { public void onClick(View view) {
switch (view.getId()) { switch (view.getId()) {
case R.id.combo_refresh: case R.id.combo_refresh:
Thread thread = new Thread(new Runnable() { Thread thread = new Thread(() -> ComboPlugin.getPlugin().refreshDataFromPump("User request"));
@Override
public void run() {
ComboPlugin.getPlugin().refreshDataFromPump("User request");
}
});
thread.start(); thread.start();
break; break;
case R.id.combo_error_history: case R.id.combo_error_history:
// TODO show popup with pump errors and comm problems // TODO show popup with pump errors and comm problems
break; break;
case R.id.combo_stats: // case R.id.combo_stats:
// TODO show TDD stats from the pump (later) // TODO show TDD stats from the pump (later)
break; // how about rather making this a pump agnostic thing, it's all in the DB,
// add a TDD tab to Treatments?
// break;
} }
} }
@ -85,99 +80,97 @@ public class ComboFragment extends SubscriberFragment implements View.OnClickLis
} }
public void updateGUI() { public void updateGUI() {
Activity activity = getActivity(); Activity fragmentActivity = getActivity();
if (activity != null) if (fragmentActivity != null)
activity.runOnUiThread(new Runnable() { fragmentActivity.runOnUiThread(() -> {
@Override ComboPlugin plugin = ComboPlugin.getPlugin();
public void run() {
ComboPlugin plugin = ComboPlugin.getPlugin();
// activity // state
String activity = plugin.getPump().activity; stateView.setText(plugin.getStateSummary());
activityView.setText(activity != null ? activity : getString(R.string.combo_action_idle)); PumpState ps = plugin.getPump().state;
if (plugin.getPump().state.errorMsg != null
|| ps.insulinState == PumpState.EMPTY
|| ps.batteryState == PumpState.EMPTY) {
stateView.setTextColor(Color.RED);
} else if (plugin.getPump().state.suspended) {
stateView.setTextColor(Color.YELLOW);
} else {
stateView.setTextColor(Color.WHITE);
}
if (plugin.isInitialized()) { // activity
// state String activity = plugin.getPump().activity;
stateView.setText(plugin.getStateSummary()); activityView.setText(activity != null ? activity : getString(R.string.combo_action_idle));
PumpState ps = plugin.getPump().state; if (plugin.isInitialized()) {
if (plugin.getPump().state.errorMsg != null // battery
|| ps.insulinState == PumpState.EMPTY if (ps.batteryState == PumpState.EMPTY) {
|| ps.batteryState == PumpState.EMPTY) { batteryView.setText("{fa-battery-empty}");
stateView.setTextColor(Color.RED); batteryView.setTextColor(Color.RED);
} else if (plugin.getPump().state.suspended) { } else if (ps.batteryState == PumpState.LOW) {
stateView.setTextColor(Color.YELLOW); batteryView.setText("{fa-battery-quarter}");
batteryView.setTextColor(Color.YELLOW);
} else if (ps.batteryState == PumpState.UNKNOWN) {
batteryView.setText("");
batteryView.setTextColor(Color.YELLOW);
} else {
batteryView.setText("{fa-battery-full}");
batteryView.setTextColor(Color.WHITE);
}
// reservoir
int reservoirLevel = plugin.getPump().reservoirLevel;
reservoirView.setText(reservoirLevel == -1 ? "" : "" + reservoirLevel + " U");
if (ps.insulinState == PumpState.LOW) {
reservoirView.setTextColor(Color.YELLOW);
} else if (ps.insulinState == PumpState.EMPTY) {
reservoirView.setTextColor(Color.RED);
} else {
reservoirView.setTextColor(Color.WHITE);
}
// last connection
CommandResult lastCmdResult = plugin.getPump().lastCmdResult;
if (lastCmdResult != null) {
String minAgo = DateUtil.minAgo(plugin.getPump().lastSuccessfulConnection);
String time = DateUtil.timeString(plugin.getPump().lastSuccessfulConnection);
if (plugin.getPump().lastSuccessfulConnection < System.currentTimeMillis() + 30 * 60 * 1000) {
lastConnectionView.setText(getString(R.string.combo_no_pump_connection, minAgo));
lastConnectionView.setTextColor(Color.RED);
}
if (plugin.getPump().lastConnectionAttempt > plugin.getPump().lastSuccessfulConnection) {
lastConnectionView.setText(R.string.combo_connect_attempt_failed);
lastConnectionView.setTextColor(Color.YELLOW);
} else { } else {
stateView.setTextColor(Color.WHITE); lastConnectionView.setText(getString(R.string.combo_last_connection_time, minAgo, time));
lastConnectionView.setTextColor(Color.WHITE);
} }
// battery // last bolus
if (ps.batteryState == PumpState.EMPTY) { Bolus bolus = plugin.getPump().lastBolus;
batteryView.setText("{fa-battery-empty}"); if (bolus != null && bolus.timestamp + 6 * 60 * 60 * 1000 >= System.currentTimeMillis()) {
batteryView.setTextColor(Color.RED); long agoMsc = System.currentTimeMillis() - bolus.timestamp;
} else if (ps.batteryState == PumpState.LOW) { double agoHours = agoMsc / 60d / 60d / 1000d;
batteryView.setText("{fa-battery-quarter}"); lastBolusView.setText(getString(R.string.combo_last_bolus,
batteryView.setTextColor(Color.YELLOW); bolus.amount,
agoHours,
getString(R.string.hoursago),
DateUtil.timeString(bolus.timestamp)));
} else { } else {
batteryView.setText("{fa-battery-full}"); lastBolusView.setText("");
batteryView.setTextColor(Color.WHITE);
} }
// reservoir // TBR
int reservoirLevel = plugin.getPump().reservoirLevel; boolean tbrActive = ps.tbrPercent != -1 && ps.tbrPercent != 100;
reservoirView.setText(reservoirLevel == -1 ? "" : "" + reservoirLevel + " U"); String tbrStr = "";
if (ps.insulinState == PumpState.LOW) { if (tbrActive) {
reservoirView.setTextColor(Color.YELLOW); long minSinceRead = (System.currentTimeMillis() - plugin.getPump().state.timestamp) / 1000 / 60;
} else if (ps.insulinState == PumpState.EMPTY) { long remaining = ps.tbrRemainingDuration - minSinceRead;
reservoirView.setTextColor(Color.RED); if (remaining >= 0) {
} else { tbrStr = getString(R.string.combo_tbr_remaining, ps.tbrPercent, remaining);
reservoirView.setTextColor(Color.WHITE); }
}
// last connection
CommandResult lastCmdResult = plugin.getPump().lastCmdResult;
if (lastCmdResult != null) {
String minAgo = DateUtil.minAgo(lastCmdResult.completionTime);
String time = DateUtil.timeString(lastCmdResult.completionTime);
if (plugin.getPump().lastSuccessfulConnection < System.currentTimeMillis() + 30 * 60 * 1000) {
lastConnectionView.setText(getString(R.string.combo_no_pump_connection, minAgo));
lastConnectionView.setTextColor(Color.RED);
}
if (plugin.getPump().lastConnectionAttempt > plugin.getPump().lastSuccessfulConnection) {
lastConnectionView.setText(R.string.combo_connect_attempt_failed);
lastConnectionView.setTextColor(Color.YELLOW);
} else {
lastConnectionView.setText(getString(R.string.combo_last_connection_time, minAgo, time));
lastConnectionView.setTextColor(Color.WHITE);
}
// last bolus
List<Bolus> history = plugin.getPump().history.bolusHistory;
if (!history.isEmpty() && history.get(0).timestamp + 6 * 60 * 60 * 1000 >= System.currentTimeMillis()) {
Bolus bolus = history.get(0);
long agoMsc = System.currentTimeMillis() - bolus.timestamp;
double agoHours = agoMsc / 60d / 60d / 1000d;
lastBolusView.setText(getString(R.string.combo_last_bolus,
bolus.amount,
agoHours,
getString(R.string.hoursago),
DateUtil.timeString(bolus.timestamp)));
} else {
lastBolusView.setText("");
}
// TBR
boolean tbrActive = ps.tbrPercent != -1 && ps.tbrPercent != 100;
String tbrStr = "";
if (tbrActive) {
long minSinceRead = (System.currentTimeMillis() - lastCmdResult.completionTime) / 1000 / 60;
long remaining = ps.tbrRemainingDuration - minSinceRead;
if (remaining >= 0) {
tbrStr = getString(R.string.combo_tbr_remaining, ps.tbrPercent, remaining);
}
}
tempBasalText.setText(tbrStr);
} }
tempBasalText.setText(tbrStr);
} }
} }
}); });

View file

@ -35,6 +35,7 @@ import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin;
import info.nightscout.androidaps.plugins.Overview.events.EventOverviewBolusProgress; 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.DecimalFormatter;
/** /**
* Created by mike on 05.08.2016. * Created by mike on 05.08.2016.
@ -51,7 +52,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
private final RuffyCommands ruffyScripter; private final RuffyCommands ruffyScripter;
// TODO access to pump (and its members) is chaotic and needs an update // TODO access to pump (and its members) is chaotic and needs an update
private ComboPump pump = new ComboPump(); private static ComboPump pump = new ComboPump();
private static ComboPlugin plugin = null; private static ComboPlugin plugin = null;
@ -128,12 +129,12 @@ public class ComboPlugin implements PluginBase, PumpInterface {
String getStateSummary() { String getStateSummary() {
PumpState ps = pump.state; PumpState ps = pump.state;
if (ps.menu == null) if (ps.menu == null)
return MainApp.sResources.getString(R.string.combo_pump_state_unreachable); return MainApp.sResources.getString(R.string.combo_pump_state_disconnected);
else if (ps.suspended && (ps.batteryState == PumpState.EMPTY || ps.insulinState == PumpState.EMPTY)) else if (ps.suspended && (ps.batteryState == PumpState.EMPTY || ps.insulinState == PumpState.EMPTY))
return MainApp.sResources.getString(R.string.combo_pump_state_suspended_due_to_error); return MainApp.sResources.getString(R.string.combo_pump_state_suspended_due_to_error);
else if (ps.suspended) else if (ps.suspended)
return MainApp.sResources.getString(R.string.combo_pump_state_suspended_by_user); return MainApp.sResources.getString(R.string.combo_pump_state_suspended_by_user);
return MainApp.sResources.getString(R.string.combo_pump_state_running); return MainApp.sResources.getString(R.string.combo_pump_state_normal);
} }
@Override @Override
@ -207,41 +208,46 @@ public class ComboPlugin implements PluginBase, PumpInterface {
return new Date(pump.lastSuccessfulConnection); return new Date(pump.lastSuccessfulConnection);
} }
/**
* Runs pump initializing if needed, checks for boluses given on the pump, updates the
* reservoir level and checks the running TBR on the pump.
*/
@Override @Override
public synchronized void refreshDataFromPump(String reason) { public synchronized void refreshDataFromPump(String reason) {
log.debug("RefreshDataFromPump called"); log.debug("RefreshDataFromPump called");
if (!pump.initialized) { if (!pump.initialized) {
runCommand(MainApp.sResources.getString(R.string.connecting), new CommandExecution() { // TODO reading profile
@Override long maxWait = System.currentTimeMillis() + 15 * 1000;
public CommandResult execute() { while (!ruffyScripter.isPumpAvailable()) {
return ruffyScripter.readPumpState(); log.debug("Waiting for ruffy service to be connected ...");
SystemClock.sleep(100);
if (System.currentTimeMillis() > maxWait) {
log.debug("ruffy service unavailable, wtf");
return;
} }
}); }
checkPumpHistory(); runCommand("Initializing", () -> ruffyScripter.readHistory(new PumpHistoryRequest()));
pump.initialized = true; pump.initialized = true;
} else {
runCommand(MainApp.sResources.getString(R.string.combo_action_refreshing), new CommandExecution() {
@Override
public CommandResult execute() {
return ruffyScripter.readHistory(new PumpHistoryRequest().reservoirLevel(true).bolusHistory(PumpHistoryRequest.LAST));
}
});
} }
runCommand("Refreshing", ruffyScripter::readReservoirLevelAndLastBolus);
// TODO fuse the below into 'sync'? or make checkForTbrMismatch jut a trigger to issue a sync if needed; don't run sync twice as is nice
// checkForTbrMismatch();
checkPumpHistory();
} }
/**
* Checks if there are any changes on the pump AAPS isn't aware of yet and if so, read the
* full pump history and update AAPS' DB.
*/
private void checkPumpHistory() { private void checkPumpHistory() {
CommandResult commandResult = runCommand("Checking pump history", false, new CommandExecution() { CommandResult commandResult = runCommand(MainApp.sResources.getString(R.string.combo_pump_action_checking_history), () ->
@Override ruffyScripter.readHistory(
public CommandResult execute() {
return ruffyScripter.readHistory(
new PumpHistoryRequest() new PumpHistoryRequest()
.reservoirLevel(true)
.bolusHistory(PumpHistoryRequest.LAST) .bolusHistory(PumpHistoryRequest.LAST)
.tbrHistory(PumpHistoryRequest.LAST) .tbrHistory(PumpHistoryRequest.LAST)
.errorHistory(PumpHistoryRequest.LAST)); .errorHistory(PumpHistoryRequest.LAST)));
}
});
if (!commandResult.success || commandResult.history == null) { if (!commandResult.success || commandResult.history == null) {
// TODO error case, command // TODO error case, command
@ -250,6 +256,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
// TODO opt, construct PumpHistoryRequest to requset only what needs updating // TODO opt, construct PumpHistoryRequest to requset only what needs updating
boolean syncNeeded = false; boolean syncNeeded = false;
PumpHistoryRequest request = new PumpHistoryRequest();
// last bolus // last bolus
List<Treatment> treatments = MainApp.getConfigBuilder().getTreatmentsFromHistory(); List<Treatment> treatments = MainApp.getConfigBuilder().getTreatmentsFromHistory();
@ -267,11 +274,10 @@ public class ComboPlugin implements PluginBase, PumpInterface {
pumpBolus = bolusHistory.get(0); pumpBolus = bolusHistory.get(0);
} }
if (aapsBolus == null || pumpBolus == null) { if ((aapsBolus == null || pumpBolus == null)
syncNeeded = true; || (Math.abs(aapsBolus.insulin - pumpBolus.amount) > 0.05 || aapsBolus.date != pumpBolus.timestamp)) {
} else if (Math.abs(aapsBolus.insulin - pumpBolus.amount) > 0.05
|| aapsBolus.date != pumpBolus.timestamp) {
syncNeeded = true; syncNeeded = true;
request.bolusHistory = PumpHistoryRequest.FULL;
} }
// last tbr // last tbr
@ -282,106 +288,89 @@ public class ComboPlugin implements PluginBase, PumpInterface {
} }
Tbr pumpTbr = null; Tbr pumpTbr = null;
List<Tbr> tbrHistory = commandResult.history.tbrHistory; List<Tbr> tbrHistory = commandResult.history.tbrHistory;
if(!tbrHistory.isEmpty()) { if (!tbrHistory.isEmpty()) {
pumpTbr = tbrHistory.get(0); pumpTbr = tbrHistory.get(0);
} }
if (aapsTbr == null || pumpTbr == null) { if ((aapsTbr == null || pumpTbr == null)
syncNeeded = true; || (aapsTbr.percentRate != pumpTbr.percent || aapsTbr.durationInMinutes != pumpTbr.duration)) {
} else if (aapsTbr.percentRate != pumpTbr.percent || aapsTbr.durationInMinutes != pumpTbr.duration) {
syncNeeded = true; syncNeeded = true;
request.tbrHistory = PumpHistoryRequest.FULL;
} }
// last error // last error
// TODO add DB table // TODO add DB table ... or just keep in memory? does android allow that (fragment kill frenzy) without workarounds?
// is comboplugin a service or a class with statics?
request.pumpErrorHistory = PumpHistoryRequest.FULL;
// tdd
// TODO; ... just fetch them on-deamand when the user opens the fragment?
if (syncNeeded) { if (syncNeeded) {
runFullSync(); runFullSync(request);
} }
// TODO
// detectStateMismatch(): expensive sync, checking everything (detectTbrMisMatch us called for every command)
// check 'lasts' of pump against treatment db, request full sync if needed
// and also remove treatments the pump doesn't have.
// warn about this with a notification? show what was removed on combo tab?
} }
// TODO uses profile values for the time being // TODO uses profile values for the time being
// this get's called multiple times a minute, must absolutely be cached
@Override @Override
public double getBaseBasalRate() { public double getBaseBasalRate() {
/* if (pump.basalProfile == null) {
// TODO when to force refresh this?
CommandResult result = runCommand("Reading basal profile", new CommandExecution() {
@Override
public CommandResult execute() {
return ruffyScripter.readBasalProfile(1);
}
});
pump.basalProfile = result.basalProfile;
// TODO error handling ...
}
return pump.basalProfile.hourlyRates[Calendar.getInstance().get(Calendar.HOUR_OF_DAY)];
*/
Profile profile = MainApp.getConfigBuilder().getProfile(); Profile profile = MainApp.getConfigBuilder().getProfile();
Double basal = profile.getBasal(); Double basal = profile.getBasal();
log.trace("getBaseBasalrate returning " + basal); log.trace("getBaseBasalrate returning " + basal);
return basal; return basal;
} }
private static BolusProgressReporter nullBolusProgressReporter = new BolusProgressReporter() { private static BolusProgressReporter nullBolusProgressReporter = (state, percent, delivered) -> {
@Override
public void report(State state, int percent, double delivered) {}
}; };
private static BolusProgressReporter bolusProgressReporter = private static BolusProgressReporter bolusProgressReporter = (state, percent, delivered) -> {
new BolusProgressReporter() { EventOverviewBolusProgress event = EventOverviewBolusProgress.getInstance();
@Override switch (state) {
public void report(BolusProgressReporter.State state, int percent, double delivered) { case PROGRAMMING:
EventOverviewBolusProgress event = EventOverviewBolusProgress.getInstance(); event.status = MainApp.sResources.getString(R.string.combo_programming_bolus);
switch (state) { break;
case PROGRAMMING: case DELIVERING:
event.status = MainApp.sResources.getString(R.string.combo_programming_bolus); event.status = String.format(MainApp.sResources.getString(R.string.bolusdelivering), delivered);
break; break;
case DELIVERING: case DELIVERED:
event.status = String.format(MainApp.sResources.getString(R.string.bolusdelivering), delivered); event.status = String.format(MainApp.sResources.getString(R.string.bolusdelivered), delivered);
break; break;
case DELIVERED: case STOPPING:
event.status = String.format(MainApp.sResources.getString(R.string.bolusdelivered), delivered); event.status = MainApp.sResources.getString(R.string.bolusstopping);
break; break;
case STOPPING: case STOPPED:
event.status = MainApp.sResources.getString(R.string.bolusstopping); event.status = MainApp.sResources.getString(R.string.bolusstopped);
break; break;
case STOPPED: case FINISHED:
event.status = MainApp.sResources.getString(R.string.bolusstopped); // no state, just percent below to close bolus progress dialog
break; break;
case FINISHED: }
// no state, just percent below to close bolus progress dialog event.percent = percent;
break; MainApp.bus().post(event);
} };
event.percent = percent;
MainApp.bus().post(event);
}
};
/** /**
* Updates Treatment records with carbs and boluses and delivers a bolus if needed * 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) {
// TODO for non-SMB: read resorvoir level first to make sure there's enough insulin left
try { try {
if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) { if (detailedBolusInfo.insulin == 0 && detailedBolusInfo.carbs == 0) {
if (detailedBolusInfo.insulin > 0) {
// bolus needed, ask pump to deliver it
return deliverBolus(detailedBolusInfo);
} else {
// no bolus required, carb only treatment
// TODO the ui freezes when the calculator issues a carb-only treatment
// so just wait, yeah, this is dumb. for now; proper fix via GL#10
// info.nightscout.androidaps.plugins.Overview.Dialogs.BolusProgressDialog.scheduleDismiss()
SystemClock.sleep(6000);
PumpEnactResult pumpEnactResult = new PumpEnactResult();
pumpEnactResult.success = true;
pumpEnactResult.enacted = true;
pumpEnactResult.bolusDelivered = 0d;
pumpEnactResult.carbsDelivered = detailedBolusInfo.carbs;
pumpEnactResult.comment = MainApp.instance().getString(R.string.virtualpump_resultok);
MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo);
EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance();
bolusingEvent.percent = 100;
MainApp.bus().post(bolusingEvent);
return pumpEnactResult;
}
} else {
// neither carbs nor bolus requested // neither carbs nor bolus requested
PumpEnactResult pumpEnactResult = new PumpEnactResult(); PumpEnactResult pumpEnactResult = new PumpEnactResult();
pumpEnactResult.success = false; pumpEnactResult.success = false;
@ -391,6 +380,24 @@ public class ComboPlugin implements PluginBase, PumpInterface {
pumpEnactResult.comment = MainApp.instance().getString(R.string.danar_invalidinput); pumpEnactResult.comment = MainApp.instance().getString(R.string.danar_invalidinput);
log.error("deliverTreatment: Invalid input"); log.error("deliverTreatment: Invalid input");
return pumpEnactResult; return pumpEnactResult;
} else if (detailedBolusInfo.insulin > 0) {
// bolus needed, ask pump to deliver it
return deliverBolus(detailedBolusInfo);
} else {
// no bolus required, carb only treatment
SystemClock.sleep(6000);
PumpEnactResult pumpEnactResult = new PumpEnactResult();
pumpEnactResult.success = true;
pumpEnactResult.enacted = true;
pumpEnactResult.bolusDelivered = 0d;
pumpEnactResult.carbsDelivered = detailedBolusInfo.carbs;
pumpEnactResult.comment = MainApp.instance().getString(R.string.virtualpump_resultok);
MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo);
EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance();
bolusingEvent.percent = 100;
MainApp.bus().post(bolusingEvent);
return pumpEnactResult;
} }
} finally { } finally {
MainApp.bus().post(new EventComboPumpUpdateGUI()); MainApp.bus().post(new EventComboPumpUpdateGUI());
@ -399,18 +406,18 @@ public class ComboPlugin implements PluginBase, PumpInterface {
@NonNull @NonNull
private PumpEnactResult deliverBolus(final DetailedBolusInfo detailedBolusInfo) { private PumpEnactResult deliverBolus(final DetailedBolusInfo detailedBolusInfo) {
CommandResult bolusCmdResult = runCommand(MainApp.sResources.getString(R.string.combo_action_bolusing), new CommandExecution() { // TODO
@Override // before non-SMB: check enough insulin is available, check we're up to date on boluses
public CommandResult execute() { // after bolus: update reservoir level and check the bolus we just did is actually there
return ruffyScripter.deliverBolus(detailedBolusInfo.insulin,
detailedBolusInfo.isSMB ? nullBolusProgressReporter : bolusProgressReporter); // retry flag: reconnect, kill warning, check if command can be restarted, restart
} CommandResult bolusCmdResult = runCommand(MainApp.sResources.getString(R.string.combo_action_bolusing), () -> ruffyScripter.deliverBolus(detailedBolusInfo.insulin,
}); detailedBolusInfo.isSMB ? nullBolusProgressReporter : bolusProgressReporter));
PumpEnactResult pumpEnactResult = new PumpEnactResult(); PumpEnactResult pumpEnactResult = new PumpEnactResult();
pumpEnactResult.success = bolusCmdResult.success; pumpEnactResult.success = bolusCmdResult.success;
pumpEnactResult.enacted = bolusCmdResult.enacted; pumpEnactResult.enacted = bolusCmdResult.enacted;
pumpEnactResult.comment = bolusCmdResult.message; // pumpEnactResult.comment = bolusCmdResult.message;
// if enacted, add bolus and carbs to treatment history // if enacted, add bolus and carbs to treatment history
if (pumpEnactResult.enacted) { if (pumpEnactResult.enacted) {
@ -421,7 +428,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
pumpEnactResult.bolusDelivered = detailedBolusInfo.insulin; pumpEnactResult.bolusDelivered = detailedBolusInfo.insulin;
pumpEnactResult.carbsDelivered = detailedBolusInfo.carbs; pumpEnactResult.carbsDelivered = detailedBolusInfo.carbs;
detailedBolusInfo.date = bolusCmdResult.completionTime; detailedBolusInfo.date = System.currentTimeMillis();
MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo); MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo);
} else { } else {
pumpEnactResult.bolusDelivered = 0d; pumpEnactResult.bolusDelivered = 0d;
@ -432,6 +439,8 @@ public class ComboPlugin implements PluginBase, PumpInterface {
@Override @Override
public void stopBolusDelivering() { public void stopBolusDelivering() {
// TODO note that we requested this, so we can thandle this proper in runCommand;
// or is it fine if the command returns success with noting enacted and history checks as well/**/
ruffyScripter.cancelBolus(); ruffyScripter.cancelBolus();
} }
@ -471,16 +480,11 @@ public class ComboPlugin implements PluginBase, PumpInterface {
} }
final int finalAdjustedPercent = adjustedPercent; final int finalAdjustedPercent = adjustedPercent;
CommandResult commandResult = runCommand(MainApp.sResources.getString(R.string.combo_action_setting_tbr), new CommandExecution() { CommandResult commandResult = runCommand(MainApp.sResources.getString(R.string.combo_action_setting_tbr), () -> ruffyScripter.setTbr(finalAdjustedPercent, durationInMinutes));
@Override
public CommandResult execute() {
return ruffyScripter.setTbr(finalAdjustedPercent, durationInMinutes);
}
}
);
if (commandResult.enacted) { if (commandResult.enacted) {
TemporaryBasal tempStart = new TemporaryBasal(commandResult.completionTime); pump.tbrSetTime = System.currentTimeMillis();
TemporaryBasal tempStart = new TemporaryBasal(pump.tbrSetTime);
// TODO commandResult.state.tbrRemainingDuration might already display 29 if 30 was set, since 29:59 is shown as 29 ... // TODO commandResult.state.tbrRemainingDuration might already display 29 if 30 was set, since 29:59 is shown as 29 ...
// we should check this, but really ... something must be really screwed up if that number was anything different // we should check this, but really ... something must be really screwed up if that number was anything different
// TODO actually ... might setting 29 help with gaps between TBRs? w/o the hack in TemporaryBasal? // TODO actually ... might setting 29 help with gaps between TBRs? w/o the hack in TemporaryBasal?
@ -496,7 +500,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
PumpEnactResult pumpEnactResult = new PumpEnactResult(); PumpEnactResult pumpEnactResult = new PumpEnactResult();
pumpEnactResult.success = commandResult.success; pumpEnactResult.success = commandResult.success;
pumpEnactResult.enacted = commandResult.enacted; pumpEnactResult.enacted = commandResult.enacted;
pumpEnactResult.comment = commandResult.message; // pumpEnactResult.comment = commandResult.message;
pumpEnactResult.isPercent = true; pumpEnactResult.isPercent = true;
// Combo would have bailed if this wasn't set properly. Maybe we should // Combo would have bailed if this wasn't set properly. Maybe we should
// have the command return this anyways ... // have the command return this anyways ...
@ -524,21 +528,16 @@ public class ComboPlugin implements PluginBase, PumpInterface {
if (activeTemp == null || userRequested) { if (activeTemp == null || userRequested) {
/* v1 compatibility to sync DB to pump if they diverged (activeTemp == null) */ /* v1 compatibility to sync DB to pump if they diverged (activeTemp == null) */
log.debug("cancelTempBasal: hard-cancelling TBR since user requested"); log.debug("cancelTempBasal: hard-cancelling TBR since user requested");
commandResult = runCommand(MainApp.sResources.getString(R.string.combo_action_cancelling_tbr), new CommandExecution() { commandResult = runCommand(MainApp.sResources.getString(R.string.combo_action_cancelling_tbr), ruffyScripter::cancelTbr);
@Override
public CommandResult execute() {
return ruffyScripter.cancelTbr();
}
});
if (commandResult.enacted) { if (commandResult.enacted) {
tempBasal = new TemporaryBasal(commandResult.completionTime); tempBasal = new TemporaryBasal(System.currentTimeMillis());
tempBasal.durationInMinutes = 0; tempBasal.durationInMinutes = 0;
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 run (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.";
// TODO check what AAPS does with this; no DB update is required; // TODO check what AAPS does with this; no DB update is required;
@ -552,15 +551,10 @@ public class ComboPlugin implements PluginBase, PumpInterface {
// on whether the TBR we're cancelling is above or below 100%. // on whether the TBR we're cancelling is above or below 100%.
final int percentage = (activeTemp.percentRate > 100) ? 110 : 90; final int 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(MainApp.sResources.getString(R.string.combo_action_setting_tbr), new CommandExecution() { commandResult = runCommand(MainApp.sResources.getString(R.string.combo_action_setting_tbr), () -> ruffyScripter.setTbr(percentage, 15));
@Override
public CommandResult execute() {
return ruffyScripter.setTbr(percentage, 15);
}
});
if (commandResult.enacted) { if (commandResult.enacted) {
tempBasal = new TemporaryBasal(commandResult.completionTime); tempBasal = new TemporaryBasal(System.currentTimeMillis());
tempBasal.durationInMinutes = 15; tempBasal.durationInMinutes = 15;
tempBasal.source = Source.USER; tempBasal.source = Source.USER;
tempBasal.percentRate = percentage; tempBasal.percentRate = percentage;
@ -576,7 +570,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
if (commandResult != null) { if (commandResult != null) {
pumpEnactResult.success = commandResult.success; pumpEnactResult.success = commandResult.success;
pumpEnactResult.enacted = commandResult.enacted; pumpEnactResult.enacted = commandResult.enacted;
pumpEnactResult.comment = commandResult.message; // pumpEnactResult.comment = commandResult.message;
} }
return pumpEnactResult; return pumpEnactResult;
} }
@ -585,23 +579,47 @@ public class ComboPlugin implements PluginBase, PumpInterface {
CommandResult execute(); CommandResult execute();
} }
private CommandResult runCommand(String status, CommandExecution commandExecution) { // TODO if there was an error (or the pump was suspended) force a resync before a bolus;
return runCommand(status, true, commandExecution); // transport a message, e.g. 'new bolus found on pump, synced, check and issue bolus again'
// back to the user?b
} private synchronized CommandResult runCommand(String activity, CommandExecution commandExecution) {
if (activity != null) {
pump.activity = activity;
MainApp.bus().post(new EventComboPumpUpdateGUI());
}
private CommandResult runCommand(String activity, boolean checkTbrMisMatch, CommandExecution commandExecution) { // CommandResult precheck = ruffyScripter.readPumpState();
pump.activity = activity; // tbrcheck?
MainApp.bus().post(new EventComboPumpUpdateGUI()); // check for active alert; if warning confirm; on warning confirm, read history (bolus, errors), which shall raise alerts if appropriate
//
// precheck.
CommandResult commandResult = commandExecution.execute(); CommandResult commandResult = commandExecution.execute();
pump.lastCmdResult = commandResult;
pump.lastConnectionAttempt = System.currentTimeMillis();
if (commandResult.success) { if (commandResult.success) {
pump.lastSuccessfulConnection = System.currentTimeMillis(); pump.lastSuccessfulConnection = System.currentTimeMillis();
} else {
pump.lastConnectionAttempt = System.currentTimeMillis();
} }
// copy over state (as supplied) so it will still be available when another command runs that doesn't return that data
pump.state = commandResult.state;
if (commandResult.reservoirLevel != -1) {
pump.reservoirLevel = commandResult.reservoirLevel;
}
if (commandResult.lastBolus != null) {
pump.lastBolus = commandResult.lastBolus;
}
if (commandResult.history != null) {
if (!commandResult.history.bolusHistory.isEmpty()) {
pump.lastBolus = commandResult.history.bolusHistory.get(0);
}
pump.history = commandResult.history;
}
// TODO hm... automatically confirm messages and return them and handle them here proper? // TODO hm... automatically confirm messages and return them and handle them here proper?
// with an option to corfirm all messages, non-critical (letting occlusion alert ring on phone and pump) // with an option to corfirm all messages, non-critical (letting occlusion alert ring on phone and pump)
// or let all alarms ring and don't try to control the pump in any way // or let all alarms ring and don't try to control the pump in any way
@ -627,105 +645,76 @@ public class ComboPlugin implements PluginBase, PumpInterface {
commandResult.state = takeOverAlarmResult.state; commandResult.state = takeOverAlarmResult.state;
}*/ }*/
pump.lastCmdResult = commandResult;
pump.state = commandResult.state;
// TODO call this explicitely when needed after/before calling this? // TODO call this explicitely when needed after/before calling this?
if (checkTbrMisMatch) { // if (checkTbrMisMatch) {
checkForTbrMismatch(); // checkForTbrMismatch();
} // }
// TODO not propely set all the time ...
if (pump.lastCmdResult == null) {
log.error("JOE: no!");
} else {
// still crashable ...
// TODO only update if command was successful? -> KeepAliveReceiver, triggering alarm on unavaiblae pump
pump.lastCmdResult.completionTime = System.currentTimeMillis();
}
// TODO merge all new history here?
if (commandResult.history != null) {
if (commandResult.history.reservoirLevel != -1) {
pump.reservoirLevel = commandResult.history.reservoirLevel;
}
pump.history = commandResult.history;
}
// TODO in the event of an error schedule a resync // TODO in the event of an error schedule a resync
pump.activity = null; if (activity != null) {
MainApp.bus().post(new EventComboPumpUpdateGUI()); pump.activity = null;
MainApp.bus().post(new EventComboPumpUpdateGUI());
}
return commandResult; return commandResult;
} }
// TODO rename to checkState or so and also check time (& date) of pump
private void checkForTbrMismatch() { private void checkForTbrMismatch() {
// detectTbrMismatch(): 'quick' check with not overhead on the pump side // detectTbrMismatch(): 'quick' check with no overhead on the pump side
// TODO check if this works with pump suspend, esp. around pump suspend there'll be syncing to do; // TODO check if this works with pump suspend, esp. around pump suspend there'll be syncing to do;
// TODO we need to tolerate differences of 1-2 minutes due to the time it takes to programm a tbr
// mismatching a 5m interval etc
TemporaryBasal aapsTbr = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()); TemporaryBasal aapsTbr = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis());
// if (true) { boolean sync = false;
//
// // not yet
// } else
if (aapsTbr == null && pump.state.tbrActive) { if (aapsTbr == null && pump.state.tbrActive) {
// pump runs TBR AAPS is unaware off // pump runs TBR AAPS is unaware off
// => fetch full history so the full TBR is added to treatments log.debug("Pump runs TBR AAPS is unaware of, reading last 3h of pump TBR history");
log.debug("JOE: sync required 1"); sync = true;
runFullSync();
} else if (aapsTbr != null && !pump.state.tbrActive) { } else if (aapsTbr != null && !pump.state.tbrActive) {
// AAPS has a TBR but the pump isn't running a TBR // AAPS has a TBR but the pump isn't running a TBR
// => remove the TBR from treatments log.debug("AAPS shows TBR but pump isn't running a TBR; deleting TBR in AAPS and reading last 3h of pump TBR history");
// => fetch full history, so that if the TBR was cancelled but ran some time we get the IOB from that partial TBR
log.debug("JOE: sync required 2");
MainApp.getDbHelper().delete(aapsTbr); MainApp.getDbHelper().delete(aapsTbr);
runFullSync(); sync = true;
} else if (aapsTbr != null && pump.state.tbrActive) { } else if (aapsTbr != null && pump.state.tbrActive) {
// both AAPS and pump have a TBR ... // both AAPS and pump have a TBR ...
if (aapsTbr.percentRate != pump.state.tbrPercent) { if (aapsTbr.percentRate != pump.state.tbrPercent) {
// ... but they have different percentages // ... but they have different percentages
// => remove TBR from treatments log.debug("TBR percentage differs between AAPS and pump; deleting TBR in AAPS and reading last 3h of pump TBR history");
// => full history sync so we get up to date on actual IOB
log.debug("JOE: sync required 3");
MainApp.getDbHelper().delete(aapsTbr); MainApp.getDbHelper().delete(aapsTbr);
runFullSync(); sync = true;
} }
int durationDiff = Math.abs(aapsTbr.getPlannedRemainingMinutes() - pump.state.tbrRemainingDuration); int durationDiff = Math.abs(aapsTbr.getPlannedRemainingMinutes() - pump.state.tbrRemainingDuration);
if (durationDiff > 2) { if (durationDiff > 2) {
// ... but they have different runtimes // ... but they have different runtimes
// ^ same as above, merge branches log.debug("TBR duration differs between AAPS and pump; deleting TBR in AAPS and reading last 3h of pump TBR history");
log.debug("JOE: sync required 4");
MainApp.getDbHelper().delete(aapsTbr); MainApp.getDbHelper().delete(aapsTbr);
runFullSync(); sync = true;
} }
} }
if (sync) {
runFullSync(new PumpHistoryRequest().tbrHistory(System.currentTimeMillis() - 3 * 60 * 60 * 1000));
}
// TODO request a loop run to (re)apply a TBR/SMB given this new information? or just wait till next iteration? // TODO request a loop run to (re)apply a TBR/SMB given this new information? or just wait till next iteration?
// could take 15m or so if there are missed SGVs ... // could take 15m or so if there are missed SGVs ...
// new sensitivity calc required, no?
} }
private void runFullSync() { private void runFullSync(final PumpHistoryRequest request) {
// TODO separate fetching and comparing CommandResult result = runCommand("Syncing full pump history", () -> ruffyScripter.readHistory(request));
if (1 == 1 ) {
log.error("Skipping full sync - not implemented yet");
return;
}
CommandResult commandResult = runCommand("Syncing full pump history", false, new CommandExecution() {
@Override
public CommandResult execute() {
return ruffyScripter.readHistory(
new PumpHistoryRequest()
.reservoirLevel(true)
.bolusHistory(PumpHistoryRequest.FULL)
.tbrHistory(PumpHistoryRequest.FULL)
.errorHistory(PumpHistoryRequest.FULL)
.tddHistory(PumpHistoryRequest.FULL)
);
}
});
// boluses
// TBRs
// errors
// TODO
} }
@ -734,44 +723,55 @@ public class ComboPlugin implements PluginBase, PumpInterface {
return OPERATION_NOT_SUPPORTED; return OPERATION_NOT_SUPPORTED;
} }
// Returns the state of the pump as it was received during last pump comms.
// TODO v2 add battery, reservoir info when we start reading that and clean up the code
@Override @Override
public JSONObject getJSONStatus() { public JSONObject getJSONStatus() {
CommandResult lastCmdResult = pump.lastCmdResult; if (!pump.initialized) {
if (lastCmdResult == null || lastCmdResult.completionTime + 5 * 60 * 1000L < System.currentTimeMillis()) {
return null; return null;
} }
try { try {
JSONObject pumpJson = new JSONObject(); JSONObject pumpJson = new JSONObject();
pumpJson.put("clock", DateUtil.toISOString(pump.lastSuccessfulConnection));
pumpJson.put("reservoir", pump.reservoirLevel);
JSONObject statusJson = new JSONObject(); JSONObject statusJson = new JSONObject();
JSONObject extendedJson = new JSONObject();
statusJson.put("status", getStateSummary()); statusJson.put("status", getStateSummary());
extendedJson.put("Version", BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION); statusJson.put("timestamp", pump.lastSuccessfulConnection);
try {
extendedJson.put("ActiveProfile", MainApp.getConfigBuilder().getProfileName());
} catch (Exception e) {
}
statusJson.put("timestamp", lastCmdResult.completionTime);
PumpState ps = pump.state;
if (ps != null) {
if (ps.tbrActive) {
extendedJson.put("TempBasalAbsoluteRate", ps.tbrRate);
extendedJson.put("TempBasalPercent", ps.tbrPercent);
extendedJson.put("TempBasalRemaining", ps.tbrRemainingDuration);
}
if (ps.errorMsg != null) {
extendedJson.put("ErrorMessage", ps.errorMsg);
}
}
// more info here .... look at dana plugin
pumpJson.put("status", statusJson); pumpJson.put("status", statusJson);
JSONObject extendedJson = new JSONObject();
extendedJson.put("Version", BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION);
extendedJson.put("ActiveProfile", MainApp.getConfigBuilder().getProfileName());
if (pump.lastBolus != null) {
extendedJson.put("LastBolus", new Date(pump.lastBolus.timestamp).toLocaleString());
extendedJson.put("LastBolusAmount", DecimalFormatter.to1Decimal(pump.lastBolus.amount));
}
PumpState ps = pump.state;
if (ps.tbrActive) {
extendedJson.put("TempBasalAbsoluteRate", ps.tbrRate);
extendedJson.put("TempBasalPercent", ps.tbrPercent);
extendedJson.put("TempBasalRemaining", ps.tbrRemainingDuration);
}
if (ps.errorMsg != null) {
extendedJson.put("ErrorMessage", ps.errorMsg);
}
pumpJson.put("extended", extendedJson); pumpJson.put("extended", extendedJson);
pumpJson.put("clock", DateUtil.toISOString(lastCmdResult.completionTime));
JSONObject batteryJson = new JSONObject();
int battery;
switch (ps.batteryState) {
case PumpState.EMPTY:
battery = 0;
break;
case PumpState.LOW:
battery = 25;
break;
default:
battery = 75;
break;
}
batteryJson.put("percent", battery);
pumpJson.put("battery", batteryJson);
return pumpJson; return pumpJson;
} catch (Exception e) { } catch (Exception e) {
@ -781,10 +781,8 @@ public class ComboPlugin implements PluginBase, PumpInterface {
return null; return null;
} }
// TODO
@Override @Override
public String deviceID() { public String deviceID() {
// Serial number here
return "Combo"; return "Combo";
} }
@ -795,7 +793,6 @@ public class ComboPlugin implements PluginBase, PumpInterface {
@Override @Override
public String shortStatus(boolean veryShort) { public String shortStatus(boolean veryShort) {
// TODO trim for wear if veryShort==true
return getStateSummary(); return getStateSummary();
} }

View file

@ -3,24 +3,31 @@ package info.nightscout.androidaps.plugins.PumpCombo;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import de.jotomo.ruffy.spi.BasalProfile;
import de.jotomo.ruffy.spi.PumpState; import de.jotomo.ruffy.spi.PumpState;
import de.jotomo.ruffy.spi.CommandResult; import de.jotomo.ruffy.spi.CommandResult;
import de.jotomo.ruffy.spi.history.Bolus;
import de.jotomo.ruffy.spi.history.PumpHistory; import de.jotomo.ruffy.spi.history.PumpHistory;
class ComboPump { class ComboPump {
// TODO actually ... this isn't about successful command execution, but whether we could connect to the pump at all // TODO all non-state (==main screen) data is overriden by commands, no? put them seperately
// at least skim over how dana does it!
boolean initialized = false; boolean initialized = false;
// TODO actually ... this isn't about successful command execution, but whether we could connect to the pump at all
volatile long lastSuccessfulConnection; volatile long lastSuccessfulConnection;
volatile long lastConnectionAttempt; volatile long lastConnectionAttempt;
@Nullable @Nullable
volatile CommandResult lastCmdResult; volatile CommandResult lastCmdResult;
public volatile String activity; public volatile String activity;
@NonNull @NonNull
volatile PumpState state = new PumpState(); volatile PumpState state = new PumpState();
volatile int reservoirLevel = -1; volatile int reservoirLevel = -1;
volatile Bolus lastBolus = null;
@Nullable
volatile BasalProfile basalProfile;
@NonNull @NonNull
volatile PumpHistory history = new PumpHistory(); volatile PumpHistory history = new PumpHistory();
/** Time the active TBR was set (if any) */
long tbrSetTime;
} }

View file

@ -56,7 +56,9 @@ public class KeepAliveReceiver extends BroadcastReceiver {
private void checkPump() { private void checkPump() {
final PumpInterface pump = MainApp.getConfigBuilder(); final PumpInterface pump = MainApp.getConfigBuilder();
final Profile profile = MainApp.getConfigBuilder().getProfile(); final Profile profile = MainApp.getConfigBuilder().getProfile();
if (pump != null && pump.isInitialized() && profile != null && profile.getBasal() != null) { if (pump != null && /* TODO does this prevent the error in case the pump is never initialized because it was never reachable? */
// pump.isInitialized() &&
profile != null && profile.getBasal() != null) {
Date lastConnection = pump.lastDataTime(); Date lastConnection = pump.lastDataTime();
boolean isStatusOutdated = lastConnection.getTime() + 15 * 60 * 1000L < System.currentTimeMillis(); boolean isStatusOutdated = lastConnection.getTime() + 15 * 60 * 1000L < System.currentTimeMillis();
@ -71,7 +73,7 @@ public class KeepAliveReceiver extends BroadcastReceiver {
// The alarm sound is played back as regular media, that means it might be muted if sound level is at 0 // The alarm sound is played back as regular media, that means it might be muted if sound level is at 0
// a simple 'Enable/disable alarms' button on the actions tab? // a simple 'Enable/disable alarms' button on the actions tab?
Notification n = new Notification(Notification.PUMP_UNREACHABLE, Notification n = new Notification(Notification.PUMP_UNREACHABLE,
MainApp.sResources.getString(R.string.combo_pump_state_unreachable), Notification.URGENT); MainApp.sResources.getString(R.string.combo_pump_state_disconnected), Notification.URGENT);
n.soundId = R.raw.alarm; n.soundId = R.raw.alarm;
MainApp.bus().post(new EventNewNotification(n)); MainApp.bus().post(new EventNewNotification(n));
} else if (SP.getBoolean("syncprofiletopump", false) && !pump.isThisProfileSet(profile)) { } else if (SP.getBoolean("syncprofiletopump", false) && !pump.isThisProfileSet(profile)) {

View file

@ -95,7 +95,7 @@
android:gravity="start" android:gravity="start"
android:paddingLeft="5dp" android:paddingLeft="5dp"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="20dp" /> android:textSize="20sp" />
</LinearLayout> </LinearLayout>
@ -360,19 +360,6 @@
android:paddingRight="0dp" android:paddingRight="0dp"
android:text="@string/combo_refresh" /> android:text="@string/combo_refresh" />
<!-- TODO v3-->
<Button
android:id="@+id/combo_stats"
style="@style/ButtonSmallFontStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawableTop="@drawable/icon_danarstats"
android:paddingLeft="0dp"
android:paddingRight="0dp"
android:visibility="gone"
android:text="@string/combo_stats" />
<Button <Button
android:id="@+id/combo_error_history" android:id="@+id/combo_error_history"
style="@style/ButtonSmallFontStyle" style="@style/ButtonSmallFontStyle"

View file

@ -684,13 +684,12 @@
<string name="pump_errors_history">Fehlerprotokol</string> <string name="pump_errors_history">Fehlerprotokol</string>
<string name="treatments_wizard_tt_label">TZ</string> <string name="treatments_wizard_tt_label">TZ</string>
<string name="combo_pump_state_label">Status</string> <string name="combo_pump_state_label">Status</string>
<string name="combo_pump_state_unreachable">Keine Verbindung zur Pumpe</string> <string name="combo_pump_state_disconnected">Keine Verbindung zur Pumpe</string>
<string name="combo_pump_state_suspended_by_user">Durch Benuzter gestoppt</string> <string name="combo_pump_state_suspended_by_user">Durch Benuzter gestoppt</string>
<string name="combo_pump_state_suspended_due_to_error">Wegen Fehler gestoppt</string> <string name="combo_pump_state_suspended_due_to_error">Wegen Fehler gestoppt</string>
<string name="combo_pump_state_running">Normaler Betrieb</string> <string name="combo_pump_state_normal">Normaler Betrieb</string>
<string name="combo_stats">Statistiken</string> <string name="combo_stats">Statistiken</string>
<string name="combo_programming_bolus">Bolusabgabe wird vorbereitet</string> <string name="combo_programming_bolus">Bolusabgabe wird vorbereitet</string>
<string name="combo_action_refreshing">Aktualisierung</string>
<string name="combo_action_cancelling_tbr">TBR wird abgebrochen</string> <string name="combo_action_cancelling_tbr">TBR wird abgebrochen</string>
<string name="combo_action_setting_tbr">TBR wird gesetzt</string> <string name="combo_action_setting_tbr">TBR wird gesetzt</string>
<string name="combo_action_bolusing">Bolus wird abgegeben</string> <string name="combo_action_bolusing">Bolus wird abgegeben</string>

View file

@ -748,7 +748,6 @@
<string name="reuse">reuse</string> <string name="reuse">reuse</string>
<string name="wearcontrol_title">Controls from Watch</string> <string name="wearcontrol_title">Controls from Watch</string>
<string name="wearcontrol_summary">Set Temp-Targets and enter Treatments from the watch.</string> <string name="wearcontrol_summary">Set Temp-Targets and enter Treatments from the watch.</string>
<!-- TODO combo: still needed? -->
<string name="connectiontimedout">Connection timed out</string> <string name="connectiontimedout">Connection timed out</string>
<string name="active"><![CDATA[<Active>]]></string> <string name="active"><![CDATA[<Active>]]></string>
<string name="waitingforestimatedbolusend" formatted="false">Waiting for estimated bolus end. Remaining %d sec.</string> <string name="waitingforestimatedbolusend" formatted="false">Waiting for estimated bolus end. Remaining %d sec.</string>
@ -766,12 +765,12 @@
<string name="combo_connect_attempt_failed">Last connect attempt failed</string> <string name="combo_connect_attempt_failed">Last connect attempt failed</string>
<string name="combo_last_connection_time">%s (%s)</string> <string name="combo_last_connection_time">%s (%s)</string>
<string name="combo_tbr_remaining">%d%% (%d remaining)</string> <string name="combo_tbr_remaining">%d%% (%d remaining)</string>
<string name="combo_last_bolus">%.2f U (%.1f %s, %s)</string> <string name="combo_last_bolus">%.1f U (%.1f %s, %s)</string>
<string name="combo_pump_state_unreachable">Pump unreachable</string> <string name="combo_pump_state_disconnected">Pump disconnected</string>
<string name="combo_pump_state_suspended_due_to_error">Suspended due to error</string> <string name="combo_pump_state_suspended_due_to_error">Suspended due to error</string>
<string name="combo_pump_state_suspended_by_user">Suspended by user</string> <string name="combo_pump_state_suspended_by_user">Suspended by user</string>
<string name="combo_pump_state_running">Running</string> <string name="combo_pump_state_normal">Normal</string>
<string name="combo_action_refreshing">Refreshing</string> <string name="combo_pump_action_checking_history">Checking pump history</string>
<string name="combo_action_cancelling_tbr">Cancelling TBR</string> <string name="combo_action_cancelling_tbr">Cancelling TBR</string>
<string name="combo_action_setting_tbr">Setting TBR</string> <string name="combo_action_setting_tbr">Setting TBR</string>
<string name="combo_action_bolusing">Bolusing</string> <string name="combo_action_bolusing">Bolusing</string>

View file

@ -1,38 +1,43 @@
package de.jotomo.ruffy.spi; package de.jotomo.ruffy.spi;
import java.util.List; import java.util.Date;
import de.jotomo.ruffy.spi.history.Bolus;
import de.jotomo.ruffy.spi.history.PumpHistory; import de.jotomo.ruffy.spi.history.PumpHistory;
public class CommandResult { public class CommandResult {
/** The request made made to the pump, like setting a TBR. */
public String request;
/** Whether the command was executed successfully. */ /** Whether the command was executed successfully. */
public boolean success; public boolean success;
/** Whether any changes were made, e.g. if a the request was to cancel a running TBR, /** Whether any changes were made, e.g. if a the request was to cancel a running TBR,
* but not TBR was active, this will be false. */ * but not TBR was active, this will be false. */
public boolean enacted; public boolean enacted;
/** Time the command completed. */
public long completionTime;
/** Null unless an unhandled exception was raised. */ /** Null unless an unhandled exception was raised. */
public Exception exception; public Exception exception;
/** (Error)message describing the result of the command. */ /** (Error)message describing the result of the command. */
public String message; // TODO work outh this message
// public String message;
/** State of the pump *after* command execution. */ /** State of the pump *after* command execution. */
public PumpState state; public PumpState state;
/** History if requested by the command. */ /** History if requested by the command. */
public PumpHistory history; public PumpHistory history;
/** Basal rate profile if requested. */ /** Basal rate profile if requested. */
public List<BasalProfile> basalProfiles; public BasalProfile basalProfile;
/** Total duration the command took. */ /** Total duration the command took. */
public String duration; public String duration;
public CommandResult() { /** Whether an alert (warning only) was confirmed. This can happen during boluses.
} * Request error history to see which errors occured. */
public boolean alertConfirmed;
/** BolusCommand: if a cancel request was successful */
public boolean wasSuccessfullyCancelled;
public CommandResult request(String request) { public int reservoirLevel = -1;
this.request = request;
return this; public Bolus lastBolus;
public long pumpTime;
public CommandResult() {
} }
public CommandResult success(boolean success) { public CommandResult success(boolean success) {
@ -45,11 +50,6 @@ public class CommandResult {
return this; return this;
} }
public CommandResult completionTime(long completionTime) {
this.completionTime = completionTime;
return this;
}
public CommandResult duration(String duration) { public CommandResult duration(String duration) {
this.duration = duration; this.duration = duration;
return this; return this;
@ -60,10 +60,10 @@ public class CommandResult {
return this; return this;
} }
public CommandResult message(String message) { // public CommandResult message(String message) {
this.message = message; // this.message = message;
return this; // return this;
} // }
public CommandResult state(PumpState state) { public CommandResult state(PumpState state) {
this.state = state; this.state = state;
@ -75,24 +75,24 @@ public class CommandResult {
return this; return this;
} }
public CommandResult basalProfile(List<BasalProfile> basalProfiles) { public CommandResult basalProfile(BasalProfile basalProfile) {
this.basalProfiles = basalProfiles; this.basalProfile = basalProfile;
return this; return this;
} }
@Override @Override
public String toString() { public String toString() {
return "CommandResult{" + return "CommandResult{" +
"request='" + request + '\'' +
", success=" + success + ", success=" + success +
", enacted=" + enacted + ", enacted=" + enacted +
", completionTime=" + completionTime +
", exception=" + exception + ", exception=" + exception +
", message='" + message + '\'' + // ", message='" + message + '\'' +
", state=" + state + ", state=" + state +
", history=" + history + ", history=" + history +
", basalProfiles=" + basalProfiles + ", basalProfile=" + basalProfile +
", duration='" + duration + '\'' + ", duration='" + duration + '\'' +
", alertConfirmed='" + alertConfirmed + '\'' +
", wasSuccessfullyCancelled='" + wasSuccessfullyCancelled + '\'' +
'}'; '}';
} }
} }

View file

@ -0,0 +1,17 @@
package de.jotomo.ruffy.spi;
public class PumpErrorCodes {
public static final int CARTRIDGE_EMPTY = 1;
public static final int BATTERY_EMPTY = 2;
public static final int AUTOMATIC_OFF = 3;
public static final int OCCLUSION = 4;
public static final int END_OF_OPERATION_BACKUP_PUMP = 5;
public static final int MECHANICAL_ERROR = 6;
public static final int ELECTRONIC_ERROR = 7;
public static final int POWER_INTERRUPT = 8;
public static final int END_OF_OPERATION_LOAN_PUMP = 9;
public static final int CARTRIDGE_ERROR = 10;
public static final int SET_NOT_PRIMED = 11;
public static final int DATA_INTERRUPTED = 12;
public static final int LANGUAGE_ERROR = 13;
}

View file

@ -2,6 +2,7 @@ package de.jotomo.ruffy.spi;
/** State displayed on the main screen of the pump. */ /** State displayed on the main screen of the pump. */
public class PumpState { public class PumpState {
public long timestamp;
public String menu = null; public String menu = null;
public boolean tbrActive = false; public boolean tbrActive = false;
/** TBR percentage. 100% means no TBR active, just the normal basal rate running. */ /** TBR percentage. 100% means no TBR active, just the normal basal rate running. */
@ -22,10 +23,11 @@ public class PumpState {
public String errorMsg; public String errorMsg;
public boolean suspended; public boolean suspended;
public static final int UNKNOWN = -1;
public static final int LOW = 1; public static final int LOW = 1;
public static final int EMPTY = 2; public static final int EMPTY = 2;
public int batteryState = - 1; public int batteryState = UNKNOWN;
public int insulinState = -1; public int insulinState = UNKNOWN;
public int activeBasalProfileNumber; public int activeBasalProfileNumber;

View file

@ -0,0 +1,14 @@
package de.jotomo.ruffy.spi;
public class PumpWarningCodes {
public static final int CARTRIDGE_LOW = 1;
public static final int BATTERY_LOW = 2;
public static final int REVIEW_TIME = 3;
public static final int CALL_FOR_UPDATE = 4;
public static final int PUMP_TIMER = 5;
public static final int TBR_CANCELLED = 6;
public static final int TBR_OVER = 7;
public static final int BOLUS_CANCELLED = 8;
public static final int LOANTIME_WARNING = 9;
public static final int BLUETOOTH_FAULT = 10;
}

View file

@ -13,81 +13,24 @@ public interface RuffyCommands {
CommandResult cancelTbr(); CommandResult cancelTbr();
// TODO read Dana code wrt to syncing and such CommandResult confirmAlert(int warningCode);
/** Confirms an active alarm on the pump. The state returned is the state after the alarm
* has been confirmed. Confirmed alerts are returned in history.pumpErrorHistory. */
@Deprecated
// TODO rename to confirmActiveAlarm (single)
// add a field activeAlarm to PumpState and put logic in ComboPlugin.
CommandResult takeOverAlarms();
// let this be an actual command in RS? or extend readPumpState wit ha flag to return confirmed errors?
/* plan:
let errors ring on the pump (all require interacting with the pump anyways and they
should not be subject to errors in AAPS/ruffy.
When connecting to the pump, read the current state. If a warning(s) is ongoing
confirm it and forward it and let AAPS display it.
Check if a previous command failed (tbr/bolus) and if so, DON'T report a warning
caused by an interrupted command.
Put the logic in AAPS: don't have readPumpState automatically confirm anything,
but read state, call takeOverAlarm(alarm)
concrete warnings???
tbr cancelled => we can almost always just confirm this, we sync aaps if neded
bolus => basically the same with SMBs, for user-initiated boluses and error should be reported
properly, but that should work since it's interactive and PumpEneact result is
or should be checked (verify it is)
deliwerTreatment knows if it's SMB or not, whether it's okay to dismiss bolus cancelled alarm.
battery low => always forward (not configurable, whole point is to make AAPS master)
cartridge low => always forward
when sync detects a significant mismatch it should alert?
big bolus on pump aaps didn't knew about???
removing a big bolus in aaps db since it's not in pump history? warn? aaps bug.
==> think this whole comm errors thing through (incl. auto/reconnect, auto-confirm
), on LoD - 1 and go back to coding after that
open: dealing with low cartridge/battery during bolus.
also: dealing with empty cartridge/battery alarms we let ring (force resync
when 'next possible'. force resync when coming from a suspended state, or any
error state in general.
if cartridge is low, check reservoir before each bolus and don't attempt
to bolus if not enough in reservoir for bolus?
(testers: bolus with cartridge very low to run in such scenarios - forget the
cartridge is low).
so: * confirm pump warnings; for tbr/bolus cancelled caused by connection loss don't
report them to the user (on disconnect set a flag 'error caused by us' or use.
also note connection lose and force history resync if appropriate(?) (LAST).
* stuff low warnings: confirm, show in combo tab and give overview notification
* keepaliver receiver raises an urgent error if there have been no pump comms in
> 25m
* errors always ring on the pump, are critical and require the pump to be interacted
with, so they're not confirmed. if encountered, they're read and also reported as
urgent on the phone. cancel again if we see the user has confirmed them on the pump?
(parent monitoring, needing to see an error).
make Combo->Status red and "Occlusion" for errors?
*/
boolean isPumpAvailable(); boolean isPumpAvailable();
boolean isPumpBusy(); boolean isPumpBusy();
// start everything with this: read pump state.
// see if there's an error active.
CommandResult readPumpState(); CommandResult readPumpState();
CommandResult readReservoirLevelAndLastBolus();
CommandResult readHistory(PumpHistoryRequest request); CommandResult readHistory(PumpHistoryRequest request);
CommandResult readBasalProfile(int number); CommandResult readBasalProfile(int number);
CommandResult setBasalProfile(BasalProfile basalProfile); CommandResult setBasalProfile(BasalProfile basalProfile);
CommandResult getDateAndTime();
CommandResult setDateAndTime(Date date); CommandResult setDateAndTime(Date date);
void requestPairing(); void requestPairing();

View file

@ -7,4 +7,12 @@ public class Bolus extends HistoryRecord {
super(timestamp); super(timestamp);
this.amount = amount; this.amount = amount;
} }
@Override
public String toString() {
return "Bolus{" +
"timestamp=" + timestamp +
", amount=" + amount +
'}';
}
} }

View file

@ -2,13 +2,28 @@ package de.jotomo.ruffy.spi.history;
public class PumpError extends HistoryRecord { public class PumpError extends HistoryRecord {
/** Code is an E for error or W for warning, followed by a single digit, e.g. W7 (TBR cancelled). */ /** Code is an E for error or W for warning, followed by a single digit, e.g. W7 (TBR cancelled). */
public final String code; // public final String code;
public final Integer warningCode;
public final Integer errorCode;
/** Error message, in the language configured on the pump. */ /** Error message, in the language configured on the pump. */
public final String message; public final String message;
public PumpError(long timestamp, String code, String message) { public PumpError(long timestamp, Integer warningCode, Integer errorCode, String message) {
super(timestamp); super(timestamp);
this.code = code; this.warningCode = warningCode;
this.errorCode = errorCode;
this.message = message; this.message = message;
} }
@Override
public String toString() {
return "PumpError{" +
"timestamp=" + timestamp +
", warningCode=" + warningCode +
", errorCode=" + errorCode +
", message='" + message + '\'' +
'}';
}
} }

View file

@ -3,10 +3,10 @@ package de.jotomo.ruffy.spi.history;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List; import java.util.List;
public class PumpHistory { public class PumpHistory {
public int reservoirLevel = -1;
@NonNull @NonNull
public List<Bolus> bolusHistory = new ArrayList<>(); public List<Bolus> bolusHistory = new ArrayList<>();
@NonNull @NonNull
@ -16,12 +16,6 @@ public class PumpHistory {
@NonNull @NonNull
public List<Tdd> tddHistory = new ArrayList<>(); public List<Tdd> tddHistory = new ArrayList<>();
public PumpHistory reservoirLevel(int reservoirLevel) {
this.reservoirLevel = reservoirLevel
;
return this;
}
public PumpHistory bolusHistory(List<Bolus> bolusHistory) { public PumpHistory bolusHistory(List<Bolus> bolusHistory) {
this.bolusHistory = bolusHistory; this.bolusHistory = bolusHistory;
return this; return this;
@ -32,7 +26,7 @@ public class PumpHistory {
return this; return this;
} }
public PumpHistory errorHistory(List<PumpError> pumpErrorHistory) { public PumpHistory pumpErrorHistory(List<PumpError> pumpErrorHistory) {
this.pumpErrorHistory = pumpErrorHistory; this.pumpErrorHistory = pumpErrorHistory;
return this; return this;
} }
@ -45,7 +39,6 @@ public class PumpHistory {
@Override @Override
public String toString() { public String toString() {
return "PumpHistory{" + return "PumpHistory{" +
"reservoirLevel=" + reservoirLevel +
", bolusHistory=" + bolusHistory.size() + ", bolusHistory=" + bolusHistory.size() +
", tbrHistory=" + tbrHistory.size() + ", tbrHistory=" + tbrHistory.size() +
", pumpErrorHistory=" + pumpErrorHistory.size() + ", pumpErrorHistory=" + pumpErrorHistory.size() +

View file

@ -2,8 +2,6 @@ package de.jotomo.ruffy.spi.history;
/** What data a 'read history' request should return. */ /** What data a 'read history' request should return. */
public class PumpHistoryRequest { public class PumpHistoryRequest {
public boolean reservoirLevel;
/* History to read: /* History to read:
Either the timestamp of the last known record to fetch all newer records, Either the timestamp of the last known record to fetch all newer records,
or one of the constants to read no history or all of it. or one of the constants to read no history or all of it.
@ -15,12 +13,6 @@ public class PumpHistoryRequest {
public long bolusHistory = SKIP; public long bolusHistory = SKIP;
public long tbrHistory = SKIP; public long tbrHistory = SKIP;
public long pumpErrorHistory = SKIP; public long pumpErrorHistory = SKIP;
public long tddHistory = SKIP;
public PumpHistoryRequest reservoirLevel(boolean reservoirLevel) {
this.reservoirLevel = reservoirLevel;
return this;
}
public PumpHistoryRequest bolusHistory(long bolusHistory) { public PumpHistoryRequest bolusHistory(long bolusHistory) {
this.bolusHistory = bolusHistory; this.bolusHistory = bolusHistory;
@ -37,19 +29,12 @@ public class PumpHistoryRequest {
return this; return this;
} }
public PumpHistoryRequest tddHistory(long tddHistory) {
this.tddHistory = tddHistory;
return this;
}
@Override @Override
public String toString() { public String toString() {
return "PumpHistoryRequest{" + return "PumpHistoryRequest{" +
"reservoirLevel=" + reservoirLevel +
", bolusHistory=" + bolusHistory + ", bolusHistory=" + bolusHistory +
", tbrHistory=" + tbrHistory + ", tbrHistory=" + tbrHistory +
", pumpErrorHistory=" + pumpErrorHistory + ", pumpErrorHistory=" + pumpErrorHistory +
", tddHistory=" + tddHistory +
'}'; '}';
} }
} }

View file

@ -9,4 +9,13 @@ public class Tbr extends HistoryRecord {
this.duration = duration; this.duration = duration;
this.percent = percent; this.percent = percent;
} }
@Override
public String toString() {
return "Tbr{" +
"timestamp=" + timestamp +
", duration=" + duration +
", percent=" + percent +
'}';
}
} }

View file

@ -8,4 +8,12 @@ public class Tdd extends HistoryRecord {
super(timestamp); super(timestamp);
this.total = total; this.total = total;
} }
@Override
public String toString() {
return "Tdd{" +
"timestamp=" + timestamp +
", total=" + total +
'}';
}
} }

View file

@ -21,12 +21,21 @@ public class RuffyCommandsV1Impl implements RuffyCommands {
return delegate; return delegate;
} }
private RuffyCommandsV1Impl() { private RuffyCommandsV1Impl() {}
@Override
public CommandResult getDateAndTime() {
return delegate.getDateAndTime();
} }
@Override @Override
public CommandResult takeOverAlarms() { public CommandResult readReservoirLevelAndLastBolus() {
return delegate.takeOverAlarms(); return delegate.readReservoirLevelAndLastBolus();
}
@Override
public CommandResult confirmAlert(int warningCode) {
return delegate.confirmAlert(warningCode);
} }
@Override @Override

View file

@ -34,12 +34,13 @@ import de.jotomo.ruffyscripter.commands.BolusCommand;
import de.jotomo.ruffyscripter.commands.CancelTbrCommand; import de.jotomo.ruffyscripter.commands.CancelTbrCommand;
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.ConfirmAlertCommand;
import de.jotomo.ruffyscripter.commands.ReadBasalProfileCommand; import de.jotomo.ruffyscripter.commands.ReadBasalProfileCommand;
import de.jotomo.ruffyscripter.commands.ReadHistoryCommand; import de.jotomo.ruffyscripter.commands.ReadHistoryCommand;
import de.jotomo.ruffyscripter.commands.ReadPumpStateCommand; import de.jotomo.ruffyscripter.commands.ReadPumpStateCommand;
import de.jotomo.ruffyscripter.commands.ReadReservoirLevelAndLastBolus;
import de.jotomo.ruffyscripter.commands.SetBasalProfileCommand; import de.jotomo.ruffyscripter.commands.SetBasalProfileCommand;
import de.jotomo.ruffyscripter.commands.SetTbrCommand; import de.jotomo.ruffyscripter.commands.SetTbrCommand;
import de.jotomo.ruffyscripter.commands.TakeOverAlarmsCommand;
// 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
@ -54,6 +55,7 @@ public class RuffyScripter implements RuffyCommands {
private static final Logger log = LoggerFactory.getLogger(RuffyScripter.class); private static final Logger log = LoggerFactory.getLogger(RuffyScripter.class);
private IRuffyService ruffyService; private IRuffyService ruffyService;
// TODO never written
private String unrecoverableError = null; private String unrecoverableError = null;
@Nullable @Nullable
@ -75,6 +77,8 @@ public class RuffyScripter implements RuffyCommands {
@Override @Override
public void fail(String message) throws RemoteException { public void fail(String message) throws RemoteException {
// 10-28 19:50:54.059 1426 1826 W RuffyScripter: [Thread-268] WARN [de.jotomo.ruffyscripter.RuffyScripter$1:78]: Ruffy warns: no connection possible
log.warn("Ruffy warns: " + message); log.warn("Ruffy warns: " + message);
} }
@ -87,13 +91,11 @@ public class RuffyScripter implements RuffyCommands {
public void rtStopped() throws RemoteException { public void rtStopped() throws RemoteException {
log.debug("rtStopped callback invoked"); log.debug("rtStopped callback invoked");
currentMenu = null; currentMenu = null;
connected = false;
} }
@Override @Override
public void rtStarted() throws RemoteException { public void rtStarted() throws RemoteException {
log.debug("rtStarted callback invoked"); log.debug("rtStarted callback invoked");
connected = true;
} }
@Override @Override
@ -115,16 +117,6 @@ public class RuffyScripter implements RuffyCommands {
synchronized (screenlock) { synchronized (screenlock) {
screenlock.notifyAll(); screenlock.notifyAll();
} }
// TODO v2 switch to using IRuffyService.isConnected, rather than guessing connectivity state
// passed on screen updates
connected = true;
// note that a WARNING_OR_ERROR menu can be a valid temporary state (cancelling TBR)
// of a running command
if (activeCmd == null && currentMenu.getType() == MenuType.WARNING_OR_ERROR) {
log.warn("Warning/error menu encountered without a command running");
}
} }
@Override @Override
@ -174,9 +166,6 @@ public class RuffyScripter implements RuffyCommands {
return started; return started;
} }
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() {
@ -184,25 +173,16 @@ public class RuffyScripter implements RuffyCommands {
try { try {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
long connectionTimeOutMs = 5000; long connectionTimeOutMs = 5000;
if (connected && activeCmd == null if (ruffyService.isConnected() && activeCmd == null
&& now > lastCmdExecutionTime + connectionTimeOutMs && now > lastCmdExecutionTime + connectionTimeOutMs) {
// don't disconnect too frequently, confuses ruffy?
&& now > lastDisconnected + 15 * 1000) {
log.debug("Disconnecting after " + (connectionTimeOutMs / 1000) + "s inactivity timeout"); log.debug("Disconnecting after " + (connectionTimeOutMs / 1000) + "s inactivity timeout");
lastDisconnected = now;
ruffyService.doRTDisconnect(); ruffyService.doRTDisconnect();
connected = false;
// 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);
} }
} catch (Exception e) { } catch (Exception e) {
// TODO do we need to catch this exception somewhere else too? right now it's log.debug("Exception in idle disconnect monitor thread, taking a break and then carrying on", e);
// converted into a command failure, but it's not classified as unrecoverable; SystemClock.sleep(10 * 1000);
// eventually we might try to recover ... check docs, there's also another
// execption we should watch interacting with a remote service.
// SecurityException was the other, when there's an AIDL mismatch;
//unrecoverableError = "Ruffy service went away";
log.debug("Exception in idle disconnect monitor thread, carrying on", e);
} }
SystemClock.sleep(1000); SystemClock.sleep(1000);
} }
@ -219,18 +199,15 @@ public class RuffyScripter implements RuffyCommands {
return runCommand(new ReadPumpStateCommand()); return runCommand(new ReadPumpStateCommand());
} }
@Override
public CommandResult readReservoirLevelAndLastBolus() {
return runCommand(new ReadReservoirLevelAndLastBolus());
}
public void returnToRootMenu() { public void returnToRootMenu() {
// returning to main menu using the 'back' key does not cause a vibration // returning to main menu using the 'back' key does not cause a vibration
MenuType menuType = getCurrentMenu().getType(); MenuType menuType = getCurrentMenu().getType();
while (menuType != MenuType.MAIN_MENU && menuType != MenuType.STOP && menuType != MenuType.WARNING_OR_ERROR) { while (menuType != MenuType.MAIN_MENU && menuType != MenuType.STOP && menuType != MenuType.WARNING_OR_ERROR) {
// if (getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
// String errorMsg = (String) getCurrentMenu().getAttribute(MenuAttribute.MESSAGE);
// confirmAlert(errorMsg, 1000);
// // TODO this isn't gonna work out ... this method can't know if something was enacted ...
// // gotta keep that state in the command instance
// throw new CommandException().success(false).enacted(false)
// .message("Warning/error " + errorMsg + " raised while returning to main menu");
// }
log.debug("Going back to main menu, currently at " + menuType); log.debug("Going back to main menu, currently at " + menuType);
pressBackKey(); pressBackKey();
waitForMenuUpdate(); waitForMenuUpdate();
@ -238,17 +215,11 @@ public class RuffyScripter implements RuffyCommands {
} }
} }
private static class Returnable { /** Always returns a CommandResult, never throws */
CommandResult cmdResult; private CommandResult runCommand(final Command cmd) {
}
/**
* Always returns a CommandResult, never throws
*/
public CommandResult runCommand(final Command cmd) {
log.debug("Attempting to run cmd: " + cmd); log.debug("Attempting to run cmd: " + cmd);
if (unrecoverableError != null) { if (unrecoverableError != null) {
return new CommandResult().success(false).enacted(false).message(unrecoverableError); return new CommandResult().success(false).enacted(false).message(unrecoverableError).state(readPumpStateInternal());
} }
List<String> violations = cmd.validateArguments(); List<String> violations = cmd.validateArguments();
@ -262,59 +233,36 @@ public class RuffyScripter implements RuffyCommands {
activeCmd = cmd; activeCmd = cmd;
long connectStart = System.currentTimeMillis(); long connectStart = System.currentTimeMillis();
ensureConnected(); ensureConnected();
final Returnable returnable = new Returnable(); log.debug("Connection ready to execute cmd " + cmd);
class CommandRunner { Thread cmdThread = new Thread(() -> {
public void run() { try {
try { // TODO fail if currentMenu is not available?
Menu localCurrentMenu = currentMenu;
// Except for GetPumpStateCommand: fail on all requests if the pump is suspended. if (localCurrentMenu == null || localCurrentMenu.getType() == MenuType.STOP) {
// All trickery of not executing but returning success, so that AAPS can non-sensically TBR away when suspended if (cmd.needsRunMode()) {
// are dangerous in the current model where commands are dispatched without checking state beforehand, so activeCmd.getResult().success(false).message("Pump is suspended but operations requires to the pump to be running");
// the above tactic would result in boluses not being applied and no warning being raised. return;
// (Doing this check on the ComboPlugin level would require the plugin to fetch state from the pump,
// deal with states changes (running/stopped), propagating that to AAPS and so on, adding more state,
// which adds complexity I don't want in v1 and which requires more up-front design to do well,
// esp. with AAPS).
// So, for v1, just check the pump is not suspended before executing commands and raising an error for all
// but the GetPumpStateCommand. For v2, we'll have to come up with a better idea how to deal with the pump's
// state. Maybe having read-only commands and write/treatment commands treated differently, or maybe
// build an abstraction on top of the commands, so that e.g. a method on RuffyScripter encapsulates checking
// pre-condititions, running one or several commands, checking-post conditions and what not.
// Or maybe stick with commands, have them specify if they can run in stop mode. Think properly at which
// level to handle state and logic.
// For now, when changing cartridges and such: tell AAPS to stop the loop, change cartridge and resume the loop.
if (currentMenu == null || currentMenu.getType() == MenuType.STOP) {
if (cmd.needsRunMode()) {
returnable.cmdResult = new CommandResult().success(false).enacted(false).message("Pump is suspended");
return;
}
} }
log.debug("Connection ready to execute cmd " + cmd);
PumpState pumpState = readPumpStateInternal();
log.debug("Pump state before running command: " + pumpState);
long cmdStartTime = System.currentTimeMillis();
cmd.setScripter(RuffyScripter.this);
returnable.cmdResult = cmd.execute();
long cmdEndTime = System.currentTimeMillis();
returnable.cmdResult.completionTime = cmdEndTime;
log.debug("Executing " + cmd + " took " + (cmdEndTime - cmdStartTime) + "ms");
} catch (CommandException e) {
returnable.cmdResult = e.toCommandResult();
} catch (Exception e) {
log.error("Unexpected exception running cmd", e);
returnable.cmdResult = new CommandResult().exception(e).message("Unexpected exception running cmd");
} finally {
lastCmdExecutionTime = System.currentTimeMillis();
} }
if (localCurrentMenu != null && localCurrentMenu.getType() == MenuType.WARNING_OR_ERROR) {
// TODO unless confirmALert command fail here and return message
}
PumpState pumpState = readPumpStateInternal();
log.debug("Pump state before running command: " + pumpState);
cmd.setScripter(RuffyScripter.this);
long cmdStartTime = System.currentTimeMillis();
cmd.execute();
long cmdEndTime = System.currentTimeMillis();
log.debug("Executing " + cmd + " took " + (cmdEndTime - cmdStartTime) + "ms");
} catch (CommandException e) {
activeCmd.getResult().message(e.getMessage());
} catch (Exception e) {
log.error("Unexpected exception running cmd", e);
activeCmd.getResult().message("Unexpected exception running cmd");
} finally {
lastCmdExecutionTime = System.currentTimeMillis();
} }
} }, cmd.getClass().getSimpleName());
Thread cmdThread = new Thread(new Runnable() {
@Override
public void run() {
new CommandRunner().run();
}
}, cmd.toString());
long executionStart = System.currentTimeMillis(); long executionStart = System.currentTimeMillis();
cmdThread.start(); cmdThread.start();
@ -331,23 +279,6 @@ public class RuffyScripter implements RuffyCommands {
maxReconnectAttempts--; maxReconnectAttempts--;
cmdThread.interrupt(); cmdThread.interrupt();
reconnect(); reconnect();
// TODO at least for bigger boluses we should check history after reconnect to make sure
// we haven't issued that bolus within the last 1-2m? in case there's a bug in the code ...
// TODO: only do the reconnect to confirm the alert, then return and let the ComboPlugin decide what to do next;
// for bolus: how ... run 'step 2': checking/reading history?! step1 being bolus delivery, so have different resume points? ggrrrmpf
// less logic in scripter; just reconnect to confirm alert if needed, then return with error;
// let CP read history.LAST to see what actually happened and then resume appropriately.
cmdThread = new Thread(new Runnable() {
@Override
public void run() {
new CommandRunner().run();
}
}, cmd.toString());
cmdThread.start();
// reset timeouts after reconnect
dynamicTimeout = calculateCmdInactivityTimeout();
overallTimeout = calculateOverallCmdTimeout();
} }
} }
@ -363,42 +294,33 @@ public class RuffyScripter implements RuffyCommands {
cmdThread.interrupt(); cmdThread.interrupt();
SystemClock.sleep(5000); SystemClock.sleep(5000);
log.error("Timed out thread dead yet? " + cmdThread.isAlive()); log.error("Timed out thread dead yet? " + cmdThread.isAlive());
return new CommandResult().success(false).enacted(false).message("Command stalled, check pump!"); activeCmd.getResult().success(false).message("Command stalled, check pump!");
break;
} }
} }
if (now > overallTimeout) { if (now > overallTimeout) {
String msg = "Command " + cmd + " timed out after 4 min, check pump!"; String msg = "Command " + cmd + " timed out after 4 min, check pump!";
log.error(msg); log.error(msg);
return new CommandResult().success(false).enacted(false).message(msg); activeCmd.getResult().success(false).message(msg);
break;
} }
} }
if (returnable.cmdResult.state == null) { activeCmd.getResult().state = readPumpStateInternal();
returnable.cmdResult.state = readPumpStateInternal(); CommandResult result = activeCmd.getResult();
if (log.isDebugEnabled()) {
long connectDurationSec = (executionStart - connectStart) / 1000;
long executionDurationSec = (System.currentTimeMillis() - executionStart) / 1000;
log.debug("Command result: " + result);
log.debug("Connect: " + connectDurationSec + "s, execution: " + executionDurationSec + "s");
} }
long connectDurationSec = (executionStart - connectStart) / 1000; return result;
long executionDurationSec = (System.currentTimeMillis() - executionStart) / 1000;
returnable.cmdResult.duration = "Connect: " + connectDurationSec + "s, execution: " + executionDurationSec + "s";
returnable.cmdResult.request = cmd.toString();
log.debug("Command result: " + returnable.cmdResult);
return returnable.cmdResult;
} catch (CommandException e) { } catch (CommandException e) {
CommandResult commandResult = e.toCommandResult(); return activeCmd.getResult().success(false).message(e.getMessage()).state(readPumpStateInternal());
if (commandResult.state == null) commandResult.state = readPumpStateInternal();
return commandResult;
} catch (Exception e) { } catch (Exception e) {
// TODO catching E here AND in CommandRunner? log.error("Unexpected exception communication with ruffy", e);
// TODO detect and report pump warnings/errors differently? return activeCmd.getResult().success(false).exception(e)
log.error("Error in ruffyscripter/ruffy", e); .message("Unexpected exception communication with ruffy: " + e.getMessage()).state(readPumpStateInternal());
try {
return new CommandResult()
.exception(e)
.message("Unexpected exception communication with ruffy: " + e.getMessage())
.state(readPumpStateInternal());
} catch (Exception e1) {
// nothing more we can try
}
return new CommandResult().exception(e).message("Unexpected exception communication with ruffy: " + e.getMessage());
} finally { } finally {
activeCmd = null; activeCmd = null;
} }
@ -420,6 +342,17 @@ public class RuffyScripter implements RuffyCommands {
* *
* @return whether the reconnect and return to main menu was successful * @return whether the reconnect and return to main menu was successful
*/ */
// TODO only reconnect, confirm the warning the disconnect caused ond then return to ComboPlugin, which shall decide whether/how to restart
// requires turning CommandResult from a return type into a command field
// TODO at least for bigger boluses we should check history after reconnect to make sure
// we haven't issued that bolus within the last 1-2m? in case there's a bug in the code ...
// TODO: only do the reconnect to confirm the alert, then return and let the ComboPlugin decide what to do next;
// for bolus: how ... run 'step 2': checking/reading history?! step1 being bolus delivery, so have different resume points? ggrrrmpf
// less logic in scripter; just reconnect to confirm alert if needed, then return with error;
// let CP read history.LAST to see what actually happened and then resume appropriately.
private boolean reconnect() { private boolean reconnect() {
try { try {
log.debug("Connection was lost, trying to reconnect"); log.debug("Connection was lost, trying to reconnect");
@ -475,58 +408,37 @@ public class RuffyScripter implements RuffyCommands {
*/ */
private void ensureConnected() { private void ensureConnected() {
try { try {
boolean menuUpdateRecentlyReceived = currentMenu != null && menuLastUpdated + 1000 > System.currentTimeMillis(); if (ruffyService.isConnected()) {
log.debug("ensureConnect, connected: " + connected + ", receiving menu updates: " + menuUpdateRecentlyReceived); log.debug("Already connected");
if (menuUpdateRecentlyReceived) {
log.debug("Pump is sending us menu updates, so we're connected");
return; return;
} }
// Occasionally the rtConnect is called a few seconds after the rtDisconnected
// callback was called, in response to your disconnect request via doRtDisconnect.
// When connecting again shortly after disconnecting, the pump sometimes fails
// to come up. So for v1, just wait. This happens rarely, so no overly fancy logic needed.
// TODO v2 see if we can do this cleaner, use isDisconnected as well maybe. GL#34.
// TODO remove this, will be in the way of quickly reconnecting after an exception and dealing
// with an alarm; we'll then see if the pump can deal with this
/* if (System.currentTimeMillis() < lastDisconnected + 10 * 1000) {
log.debug("Waiting 10s to let pump settle after recent disconnect");
SystemClock.sleep(10 * 1000);
}*/
boolean connectInitSuccessful = ruffyService.doRTConnect() == 0; boolean connectInitSuccessful = ruffyService.doRTConnect() == 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
// the rtDisconnected callback sets currentMenu = null. However, there were
// race conditions, so it was removed. And really, waiting for an update
// to come in is a much safer bet.
// waitForMenuUpdate times out after 60s and throws a CommandException.
// if the user just pressed a button on the combo, the screen needs to time first
// before a connection is possible. In that case, it takes 45s before the
// connection comes up.
// waitForMenuUpdate(90, "Timeout connecting to pump");
long timeoutExpired = System.currentTimeMillis() + 90 * 1000; long timeoutExpired = System.currentTimeMillis() + 90 * 1000;
long initialUpdateTime = menuLastUpdated; long initialUpdateTime = menuLastUpdated;
long again = System.currentTimeMillis() + 30 * 1000; long again = System.currentTimeMillis() + 30 * 1000;
while (initialUpdateTime == menuLastUpdated) { while (initialUpdateTime == menuLastUpdated) {
if (System.currentTimeMillis() > timeoutExpired) { if (System.currentTimeMillis() > timeoutExpired) {
throw new CommandException().message("Timeout connecting to pump"); throw new CommandException("Timeout connecting to pump");
} }
SystemClock.sleep(50); SystemClock.sleep(50);
if (again < System.currentTimeMillis()) { if (again < System.currentTimeMillis()) {
// TODO test // TODO test
log.debug("Connecting taking long, forcing disconnect first");
ruffyService.doRTDisconnect(); ruffyService.doRTDisconnect();
SystemClock.sleep(2000); SystemClock.sleep(2000);
log.debug("Connecting again");
ruffyService.doRTConnect(); ruffyService.doRTConnect();
SystemClock.sleep(1000);
again = System.currentTimeMillis() + 30 * 1000; again = System.currentTimeMillis() + 30 * 1000;
} }
} }
} catch (CommandException e) { } catch (CommandException e) {
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
throw new CommandException().exception(e).message("Unexpected exception while initiating/restoring pump connection"); throw new CommandException("Unexpected exception while initiating/restoring pump connection", e);
} }
} }
@ -539,6 +451,7 @@ public class RuffyScripter implements RuffyCommands {
*/ */
public PumpState readPumpStateInternal() { public PumpState readPumpStateInternal() {
PumpState state = new PumpState(); PumpState state = new PumpState();
state.timestamp = System.currentTimeMillis();
Menu menu = currentMenu; Menu menu = currentMenu;
if (menu == null) { if (menu == null) {
return state; return state;
@ -571,6 +484,16 @@ public class RuffyScripter implements RuffyCommands {
return state; return state;
} }
public int readWarningCode() {
verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR);
Integer warningCode = (Integer) getCurrentMenu().getAttribute(MenuAttribute.WARNING);
while (warningCode == null) {
waitForScreenUpdate();
warningCode = (Integer) getCurrentMenu().getAttribute(MenuAttribute.WARNING);
}
return warningCode;
}
// 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
@ -625,7 +548,7 @@ public class RuffyScripter implements RuffyCommands {
// TODO force reconnect? and retry? // TODO force reconnect? and retry?
while (currentMenu == null) { while (currentMenu == null) {
if (System.currentTimeMillis() > timeout) { if (System.currentTimeMillis() > timeout) {
throw new CommandException().message("Unable to read current menu"); throw new CommandException("Unable to read current menu");
} }
log.debug("currentMenu == null, waiting"); log.debug("currentMenu == null, waiting");
waitForMenuUpdate(); waitForMenuUpdate();
@ -678,21 +601,20 @@ public class RuffyScripter implements RuffyCommands {
ruffyService.rtSendKey(Key.NO_KEY, true); ruffyService.rtSendKey(Key.NO_KEY, true);
log.debug("Releasing key"); log.debug("Releasing key");
} catch (Exception e) { } catch (Exception e) {
throw new CommandException().exception(e).message("Error while pressing buttons"); throw new CommandException("Error while pressing buttons");
} }
} }
// TODO sort out usages of this method and waitForMenu update, which have the same intent, // TODO sort out usages of this method and waitForMenu update, which have the same intent,
// but approach things differently; // but approach things differently;
public boolean waitForScreenUpdate(long timeout) { private void waitForScreenUpdate() {
synchronized (screenlock) { synchronized (screenlock) {
try { try {
screenlock.wait(timeout); screenlock.wait((long) 2000); // usually ~500, occassionally up to 1100ms
} catch (Exception e) { } catch (Exception e) {
return false; log.debug("Ignoring exception in wait for screenlock", e);
} }
} }
return true;
} }
// TODO v2, rework these two methods: waitForMenuUpdate shoud only be used by commands // TODO v2, rework these two methods: waitForMenuUpdate shoud only be used by commands
@ -702,54 +624,19 @@ public class RuffyScripter implements RuffyCommands {
// TODO confirmAlarms? and report back which were cancelled? // TODO confirmAlarms? and report back which were cancelled?
/**
* Confirms and dismisses the given alert if it's raised before the timeout
*/
public boolean confirmAlert(String alertMessage, int maxWaitMs) {
long inFiveSeconds = System.currentTimeMillis() + maxWaitMs;
boolean alertProcessed = false;
while (System.currentTimeMillis() < inFiveSeconds && !alertProcessed) {
if (getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
// 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 quick try if the can't make reading the error code work ..
String errorMsg = (String) getCurrentMenu().getAttribute(MenuAttribute.MESSAGE);
if (!errorMsg.equals(alertMessage)) {
throw new CommandException().success(false).enacted(false)
.message("An alert other than the expected " + alertMessage + " was raised by the pump: "
+ errorMsg + ". Please check the pump.");
}
// confirm alert
verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR);
pressCheckKey();
// dismiss alert
verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR);
pressCheckKey();
waitForMenuToBeLeft(MenuType.WARNING_OR_ERROR);
alertProcessed = true;
}
SystemClock.sleep(10);
}
return alertProcessed;
}
/** /**
* Wait until the menu is updated * Wait until the menu is updated
*/ */
public void waitForMenuUpdate() { public void waitForMenuUpdate() {
waitForMenuUpdate(60, "Timeout waiting for menu update"); waitForScreenUpdate();
} // long timeoutExpired = System.currentTimeMillis() + 60 * 1000;
// long initialUpdateTime = menuLastUpdated;
private void waitForMenuUpdate(long timeoutInSeconds, String errorMessage) { // while (initialUpdateTime == menuLastUpdated) {
long timeoutExpired = System.currentTimeMillis() + timeoutInSeconds * 1000; // if (System.currentTimeMillis() > timeoutExpired) {
long initialUpdateTime = menuLastUpdated; // throw new CommandException("Timeout waiting for menu update");
while (initialUpdateTime == menuLastUpdated) { // }
if (System.currentTimeMillis() > timeoutExpired) { // SystemClock.sleep(10);
throw new CommandException().message(errorMessage); // }
}
SystemClock.sleep(50);
}
} }
private void pressKey(final byte key) { private void pressKey(final byte key) {
@ -758,7 +645,7 @@ public class RuffyScripter implements RuffyCommands {
SystemClock.sleep(150); SystemClock.sleep(150);
ruffyService.rtSendKey(Key.NO_KEY, true); ruffyService.rtSendKey(Key.NO_KEY, true);
} catch (Exception e) { } catch (Exception e) {
throw new CommandException().exception(e).message("Error while pressing buttons"); throw new CommandException("Error while pressing buttons");
} }
} }
@ -775,7 +662,7 @@ public class RuffyScripter implements RuffyCommands {
// + ". Check menu settings on your pump to ensure it's not hidden."); // + ". Check menu settings on your pump to ensure it's not hidden.");
// } // }
if (retries == 0) { if (retries == 0) {
throw new CommandException().message("Menu not found searching for " + desiredMenu throw new CommandException("Menu not found searching for " + desiredMenu
+ ". Check menu settings on your pump to ensure it's not hidden."); + ". Check menu settings on your pump to ensure it's not hidden.");
} }
pressMenuKey(); pressMenuKey();
@ -792,7 +679,7 @@ public class RuffyScripter implements RuffyCommands {
long timeout = System.currentTimeMillis() + 60 * 1000; long timeout = System.currentTimeMillis() + 60 * 1000;
while (getCurrentMenu().getType() == menuType) { while (getCurrentMenu().getType() == menuType) {
if (System.currentTimeMillis() > timeout) { if (System.currentTimeMillis() > timeout) {
throw new CommandException().message("Timeout waiting for menu " + menuType + " to be left"); throw new CommandException("Timeout waiting for menu " + menuType + " to be left");
} }
SystemClock.sleep(10); SystemClock.sleep(10);
} }
@ -812,7 +699,7 @@ public class RuffyScripter implements RuffyCommands {
if (failureMessage == null) { if (failureMessage == null) {
failureMessage = "Invalid pump state, expected to be in menu " + expectedMenu + ", but current menu is " + currentMenu.getType(); failureMessage = "Invalid pump state, expected to be in menu " + expectedMenu + ", but current menu is " + currentMenu.getType();
} }
throw new CommandException().message(failureMessage); throw new CommandException(failureMessage);
} }
} }
} }
@ -824,7 +711,7 @@ public class RuffyScripter implements RuffyCommands {
SystemClock.sleep(100); SystemClock.sleep(100);
retries = retries - 1; retries = retries - 1;
} else { } else {
throw new CommandException().message("Invalid pump state, expected to be in menu MAIN or STOP but current menu is " + currentMenu.getType()); throw new CommandException("Invalid pump state, expected to be in menu MAIN or STOP but current menu is " + currentMenu.getType());
} }
} }
} }
@ -835,10 +722,10 @@ public class RuffyScripter implements RuffyCommands {
Object value = getCurrentMenu().getAttribute(attribute); Object value = getCurrentMenu().getAttribute(attribute);
while (!expectedType.isInstance(value)) { while (!expectedType.isInstance(value)) {
value = getCurrentMenu().getAttribute(attribute); value = getCurrentMenu().getAttribute(attribute);
waitForScreenUpdate(1000); waitForScreenUpdate();
retries--; retries--;
if (retries == 0) { if (retries == 0) {
throw new CommandException().message("Failed to read blinkng value: " + attribute + "=" + value + " type=" + value); throw new CommandException("Failed to read blinkng value: " + attribute + "=" + value + " type=" + value);
} }
} }
return (T) value; return (T) value;
@ -867,8 +754,8 @@ public class RuffyScripter implements RuffyCommands {
} }
@Override @Override
public CommandResult takeOverAlarms() { public CommandResult confirmAlert(int warningCode) {
return runCommand(new TakeOverAlarmsCommand()); return runCommand(new ConfirmAlertCommand(warningCode));
} }
@Override @Override
@ -886,10 +773,15 @@ public class RuffyScripter implements RuffyCommands {
return runCommand(new SetBasalProfileCommand(basalProfile)); return runCommand(new SetBasalProfileCommand(basalProfile));
} }
@Override
public CommandResult getDateAndTime() {
return new CommandResult().success(false).enacted(false);
}
@Override @Override
public CommandResult setDateAndTime(Date date) { public CommandResult setDateAndTime(Date date) {
// TODO I'm a faker! // TODO
return new CommandResult().success(true).enacted(false); return new CommandResult().success(false).enacted(false);
} }
@Override @Override
@ -906,4 +798,36 @@ public class RuffyScripter implements RuffyCommands {
public void unpair() { public void unpair() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
/**
* Confirms and dismisses the given alert if it's raised before the timeout
*/
public boolean confirmAlert(int warningCode, int maxWaitMs) {
long timeout = System.currentTimeMillis() + maxWaitMs;
while (System.currentTimeMillis() < timeout) {
if (getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
// 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.
int displayedWarningCode = readWarningCode();
String errorMsg = (String) getCurrentMenu().getAttribute(MenuAttribute.MESSAGE);
if (displayedWarningCode != warningCode) {
throw new CommandException("An alert other than the expected warning " + warningCode+ " was raised by the pump: "
+ displayedWarningCode + "(" + errorMsg + "). Please check the pump.");
}
// confirm alert
verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR);
pressCheckKey();
// dismiss alert
verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR);
pressCheckKey();
/* // TODO multiple alerts in a row ... can this occur in non-freak circumstances?
waitForMenuToBeLeft(MenuType.WARNING_OR_ERROR);*/
return true;
}
SystemClock.sleep(10);
}
return false;
}
} }

View file

@ -1,11 +1,17 @@
package de.jotomo.ruffyscripter.commands; package de.jotomo.ruffyscripter.commands;
import java.util.Collections;
import java.util.List;
import de.jotomo.ruffy.spi.CommandResult;
import de.jotomo.ruffyscripter.RuffyScripter; import de.jotomo.ruffyscripter.RuffyScripter;
public abstract class BaseCommand implements Command { public abstract class BaseCommand implements Command {
// RS will inject itself here // RS will inject itself here
protected RuffyScripter scripter; protected RuffyScripter scripter;
protected CommandResult result = new CommandResult();
@Override @Override
public void setScripter(RuffyScripter scripter) { public void setScripter(RuffyScripter scripter) {
this.scripter = scripter; this.scripter = scripter;
@ -18,8 +24,23 @@ public abstract class BaseCommand implements Command {
// TODO i18n; can we work with error codes instead of messages? Like W07? that way we're language agnostic // TODO i18n; can we work with error codes instead of messages? Like W07? that way we're language agnostic
// error message ist still needed to cancel TBR though, let next-gen ruffy take care of that? // error message ist still needed to cancel TBR though, let next-gen ruffy take care of that?
/** An alarm (or null) caused by a disconnect we can safely confirm on reconnect,
* knowing it's not severe as it was caused by this command. */ /**
* An alarm (or null) caused by a disconnect we can safely confirm on reconnect,
* knowing it's not severe as it was caused by this command.
*/
@Override @Override
public String getReconnectAlarm() { return null; } public String getReconnectAlarm() {
return null;
}
@Override
public List<String> validateArguments() {
return Collections.emptyList();
}
@Override
public CommandResult getResult() {
return result;
}
} }

View file

@ -9,15 +9,18 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale;
import de.jotomo.ruffy.spi.history.Bolus;
import de.jotomo.ruffy.spi.history.PumpHistory;
import de.jotomo.ruffyscripter.RuffyScripter;
import de.jotomo.ruffy.spi.BolusProgressReporter; import de.jotomo.ruffy.spi.BolusProgressReporter;
import de.jotomo.ruffy.spi.CommandResult; import de.jotomo.ruffy.spi.CommandResult;
import de.jotomo.ruffy.spi.PumpWarningCodes;
import de.jotomo.ruffyscripter.RuffyScripter;
import static de.jotomo.ruffy.spi.BolusProgressReporter.State.*; import static de.jotomo.ruffy.spi.BolusProgressReporter.State.DELIVERED;
import static de.jotomo.ruffy.spi.BolusProgressReporter.State.DELIVERING;
import static de.jotomo.ruffy.spi.BolusProgressReporter.State.FINISHED;
import static de.jotomo.ruffy.spi.BolusProgressReporter.State.PROGRAMMING;
import static de.jotomo.ruffy.spi.BolusProgressReporter.State.STOPPED;
import static de.jotomo.ruffy.spi.BolusProgressReporter.State.STOPPING;
public class BolusCommand extends BaseCommand { public class BolusCommand extends BaseCommand {
private static final Logger log = LoggerFactory.getLogger(BolusCommand.class); private static final Logger log = LoggerFactory.getLogger(BolusCommand.class);
@ -26,14 +29,10 @@ public class BolusCommand extends BaseCommand {
private final BolusProgressReporter bolusProgressReporter; private final BolusProgressReporter bolusProgressReporter;
private volatile boolean cancelRequested; private volatile boolean cancelRequested;
// TODO make CommandResult a field that is updated as the command progress so that we can
// (also) see via cmdResult.enacted if something has been enacted and how safe it is to
// restart the command (also checking history of course, but lets double check this cruical
// part)
public BolusCommand(double bolus, BolusProgressReporter bolusProgressReporter) { public BolusCommand(double bolus, BolusProgressReporter bolusProgressReporter) {
this.bolus = bolus; this.bolus = bolus;
this.bolusProgressReporter = bolusProgressReporter; this.bolusProgressReporter = bolusProgressReporter;
this.result = new CommandResult();
} }
@Override @Override
@ -53,28 +52,26 @@ public class BolusCommand extends BaseCommand {
} }
@Override @Override
public CommandResult execute() { public void execute() {
try { try {
// TODO read reservoir level and reject request if reservoir < bolus
// TODO also check if there's a bolus in history we're not aware of
// press check twice to get reservoir level and last bolus quickly
bolusProgressReporter.report(PROGRAMMING, 0, 0); bolusProgressReporter.report(PROGRAMMING, 0, 0);
enterBolusMenu(); enterBolusMenu();
inputBolusAmount(); inputBolusAmount();
verifyDisplayedBolusAmount(); verifyDisplayedBolusAmount();
// last chance to abort before confirm the bolus
if (cancelRequested) { if (cancelRequested) {
bolusProgressReporter.report(STOPPING, 0, 0); bolusProgressReporter.report(STOPPING, 0, 0);
scripter.returnToRootMenu(); scripter.returnToRootMenu();
bolusProgressReporter.report(STOPPED, 0, 0); bolusProgressReporter.report(STOPPED, 0, 0);
return new CommandResult().success(true).enacted(false) result.success = true;
.message("Bolus cancelled as per user request with no insulin delivered"); return;
} }
// confirm bolus // confirm bolus
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER); scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
scripter.pressCheckKey(); scripter.pressCheckKey();
result.enacted = true;
// the pump displays the entered bolus and waits a few seconds to let user check and cancel // the pump displays the entered bolus and waits a few seconds to let user check and cancel
while (scripter.getCurrentMenu().getType() == MenuType.BOLUS_ENTER) { while (scripter.getCurrentMenu().getType() == MenuType.BOLUS_ENTER) {
@ -83,47 +80,47 @@ public class BolusCommand extends BaseCommand {
scripter.pressUpKey(); scripter.pressUpKey();
// wait up to 1s for a BOLUS_CANCELLED alert, if it doesn't happen we missed // 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 // the window, simply continue and let the next cancel attempt try its luck
boolean alertWasCancelled = scripter.confirmAlert("BOLUS CANCELLED", 1000); boolean alertWasCancelled = scripter.confirmAlert(PumpWarningCodes.BOLUS_CANCELLED, 1000);
if (alertWasCancelled) { if (alertWasCancelled) {
bolusProgressReporter.report(STOPPED, 0, 0); bolusProgressReporter.report(STOPPED, 0, 0);
return new CommandResult().success(true).enacted(false) result.success = true;
.message("Bolus cancelled as per user request with no insulin delivered"); return;
} }
SystemClock.sleep(10);
} }
SystemClock.sleep(10);
} }
// the bolus progress is displayed on the main menu
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU, scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU,
"Pump did not return to MAIN_MEU from BOLUS_ENTER to deliver bolus. " "Pump did not return to MAIN_MEU from BOLUS_ENTER to deliver bolus. "
+ "Check pump manually, the bolus might not have been delivered."); + "Check pump manually, the bolus might not have been delivered.");
bolusProgressReporter.report(DELIVERING, 0, 0); bolusProgressReporter.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; // wait for bolus delivery to complete; the remaining units to deliver are counted down
// it must be cleared so we can see the remaining bolus again; boolean cancelInProgress = false;
double lastBolusReported = 0;
Double bolusRemaining = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS_REMAINING);
while (bolusRemaining != null) { while (bolusRemaining != null) {
if (cancelRequested) { if (cancelRequested && !cancelInProgress) {
// cancel running bolus by pressing up for 3s, while raise a BOLUS CANCELLED
// alert, unless the bolus finished within those 3s.
bolusProgressReporter.report(STOPPING, 0, 0); bolusProgressReporter.report(STOPPING, 0, 0);
scripter.pressKeyMs(RuffyScripter.Key.UP, 3000); cancelInProgress = true;
bolusProgressReporter.report(STOPPED, 0, 0); new Thread(() -> scripter.pressKeyMs(RuffyScripter.Key.UP, 3000), "bolus-canceller").start();
// if the bolus finished while we attempted to cancel it, there'll be no alarm }
long timeout = System.currentTimeMillis() + 2000; if (scripter.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
while (scripter.getCurrentMenu().getType() != MenuType.WARNING_OR_ERROR && System.currentTimeMillis() < timeout) { // confirm warning alerts and update the result to indicate alerts occurred
SystemClock.sleep(10); int warningCode = scripter.readWarningCode();
if (warningCode == PumpWarningCodes.BOLUS_CANCELLED) {
scripter.confirmAlert(PumpWarningCodes.BOLUS_CANCELLED, 2000);
bolusProgressReporter.report(STOPPED, 0, 0);
result.wasSuccessfullyCancelled = true;
result.alertConfirmed = true;
} else if (warningCode == PumpWarningCodes.CARTRIDGE_LOW) {
scripter.confirmAlert(PumpWarningCodes.CARTRIDGE_LOW, 2000);
result.alertConfirmed = true;
} else if (warningCode == PumpWarningCodes.BATTERY_LOW) {
scripter.confirmAlert(PumpWarningCodes.BATTERY_LOW, 2000);
result.alertConfirmed = true;
} }
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) { if (lastBolusReported != bolusRemaining) {
log.debug("Delivering bolus, remaining: " + bolusRemaining); log.debug("Delivering bolus, remaining: " + bolusRemaining);
@ -132,94 +129,13 @@ public class BolusCommand extends BaseCommand {
lastBolusReported = bolusRemaining; lastBolusReported = bolusRemaining;
} }
/*
// TODO think through situatiotns where an alarm can be raised, not just when pressing a button,
// but a 'low battery' alarm can trigger at any time ...
if (scripter.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
String message = (String) scripter.getCurrentMenu().getAttribute(MenuAttribute.MESSAGE);
if (message.equals("LOW CARTRIDGE")) {
lowCartdrigeAlarmTriggered = true;
scripter.confirmAlert("LOW CARTRIDGE", 2000);
} else {
// any other alert
break;
}
}
*/
SystemClock.sleep(50); SystemClock.sleep(50);
bolusRemaining = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS_REMAINING); bolusRemaining = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS_REMAINING);
} }
bolusProgressReporter.report(DELIVERED, 100, bolus); bolusProgressReporter.report(DELIVERED, 100, bolus);
result.success = true;
/*
// wait up to 2s for any possible warning to be raised, if not raised already
// TODO what could be raised here, other than those alarms than can ring at any time anyways?
long timeout = System.currentTimeMillis() + 2 * 1000;
while (scripter.getCurrentMenu().getType() != MenuType.WARNING_OR_ERROR && System.currentTimeMillis() < timeout) {
SystemClock.sleep(50);
}
// process warnings (confirm them, report back to AAPS about them)
// while (scripter.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR || System.currentTimeMillis() < timeout) {
if (scripter.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
scripter.confirmAlert(((String) scripter.getCurrentMenu().getAttribute(MenuAttribute.MESSAGE)), 1000);
}
// SystemClock.sleep(50);
// }
*/
// 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 ReadHistory(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");
// TODO how would this call fail? more generally ......
scripter.returnToRootMenu();
if (scripter.getCurrentMenu().getType() != MenuType.MAIN_MENU) {
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.");
}
ArrayList<Bolus> boluses = new ArrayList<>();
boluses.add(new Bolus(System.currentTimeMillis(), bolus));
return new CommandResult().success(true).enacted(true)
.message(String.format(Locale.US, "Delivered %02.1f U", bolus))
.history(new PumpHistory().bolusHistory(boluses));
} catch (CommandException e) {
return e.toCommandResult();
} finally { } finally {
bolusProgressReporter.report(null, 100, 0); bolusProgressReporter.report(FINISHED, 100, 0);
} }
} }
@ -257,7 +173,7 @@ public class BolusCommand extends BaseCommand {
log.debug("Final bolus: " + displayedBolus); log.debug("Final bolus: " + displayedBolus);
if (Math.abs(displayedBolus - bolus) > 0.05) { if (Math.abs(displayedBolus - bolus) > 0.05) {
throw new CommandException().message("Failed to set correct bolus. Expected: " + bolus + ", actual: " + displayedBolus); throw new CommandException("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 // check again to ensure the displayed value hasn't change due to due scrolling taking extremely long
@ -265,7 +181,7 @@ public class BolusCommand extends BaseCommand {
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER); scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
double refreshedDisplayedBolus = scripter.readBlinkingValue(Double.class, MenuAttribute.BOLUS); double refreshedDisplayedBolus = scripter.readBlinkingValue(Double.class, MenuAttribute.BOLUS);
if (Math.abs(displayedBolus - refreshedDisplayedBolus) > 0.05) { if (Math.abs(displayedBolus - refreshedDisplayedBolus) > 0.05) {
throw new CommandException().message("Failed to set bolus: bolus changed after input stopped from " throw new CommandException("Failed to set bolus: bolus changed after input stopped from "
+ displayedBolus + " -> " + refreshedDisplayedBolus); + displayedBolus + " -> " + refreshedDisplayedBolus);
} }
} }

View file

@ -4,11 +4,7 @@ import org.monkey.d.ruffy.ruffy.driver.display.MenuType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.List;
import de.jotomo.ruffy.spi.PumpState; import de.jotomo.ruffy.spi.PumpState;
import de.jotomo.ruffy.spi.CommandResult;
// 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
@ -16,41 +12,21 @@ import de.jotomo.ruffy.spi.CommandResult;
public class CancelTbrCommand extends BaseCommand { public class CancelTbrCommand extends BaseCommand {
private static final Logger log = LoggerFactory.getLogger(CancelTbrCommand.class); private static final Logger log = LoggerFactory.getLogger(CancelTbrCommand.class);
@Override
public List<String> validateArguments() {
return Collections.emptyList();
}
@Override @Override
public String getReconnectAlarm() { public String getReconnectAlarm() {
return "TBR CANCELLED"; return "TBR CANCELLED";
} }
@Override @Override
public CommandResult execute() { public void execute() {
try { scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU);
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU); PumpState pumpState = scripter.readPumpStateInternal();
PumpState pumpState = scripter.readPumpStateInternal(); log.debug("Cancelling active TBR of " + pumpState.tbrPercent
if (!pumpState.tbrActive) { + "% with " + pumpState.tbrRemainingDuration + " min remaining");
log.debug("No TBR active to cancel"); SetTbrCommand setTbrCommand = new SetTbrCommand(100, 0);
return new CommandResult() setTbrCommand.setScripter(scripter);
.success(true) setTbrCommand.execute();
// Technically, nothing was enacted, but AAPS needs this to recover result = setTbrCommand.result;
// when there was an issue and AAPS thinks a TBR is still active,
// so the ComboPlugin can create a TempporaryBasel to mark the TBR
// as finished to get in sync with the pump state.
.enacted(true)
.message("No TBR active");
}
log.debug("Cancelling active TBR of " + pumpState.tbrPercent
+ "% with " + pumpState.tbrRemainingDuration + " min remaining");
SetTbrCommand setTbrCommand = new SetTbrCommand(100, 0);
setTbrCommand.setScripter(scripter);
return setTbrCommand.execute();
} catch (CommandException e) {
return e.toCommandResult();
}
} }
@Override @Override

View file

@ -13,9 +13,10 @@ import de.jotomo.ruffy.spi.CommandResult;
* sequence, letting the methods take care of waits. * sequence, letting the methods take care of waits.
*/ */
public interface Command { public interface Command {
CommandResult execute();
List<String> validateArguments();
void setScripter(RuffyScripter scripter); void setScripter(RuffyScripter scripter);
List<String> validateArguments();
boolean needsRunMode(); boolean needsRunMode();
void execute();
CommandResult getResult();
String getReconnectAlarm(); String getReconnectAlarm();
} }

View file

@ -1,52 +1,11 @@
package de.jotomo.ruffyscripter.commands; package de.jotomo.ruffyscripter.commands;
import de.jotomo.ruffy.spi.CommandResult;
public class CommandException extends RuntimeException { public class CommandException extends RuntimeException {
public boolean success = false; public CommandException(String message) {
public boolean enacted = false; super(message);
public Exception exception = null;
public String message = null;
public CommandException() {
} }
public CommandException success(boolean success) { public CommandException(String message, Exception exception) {
this.success = success; super(message, exception);
return this;
}
public CommandException enacted(boolean enacted) {
this.enacted = enacted;
return this;
}
public CommandException exception(Exception exception) {
this.exception = exception;
return this;
}
@Override
public String getMessage() {
return message;
}
public CommandException message(String message) {
this.message = message;
return this;
}
public CommandResult toCommandResult() {
return new CommandResult().success(success).enacted(enacted).exception(exception).message(message);
}
@Override
public String toString() {
return "CommandException{" +
"success=" + success +
", enacted=" + enacted +
", exception=" + exception +
", message='" + message + '\'' +
'}';
} }
} }

View file

@ -0,0 +1,14 @@
package de.jotomo.ruffyscripter.commands;
public class ConfirmAlertCommand extends BaseCommand {
private final int warningCode;
public ConfirmAlertCommand(int warningCode) {
this.warningCode = warningCode;
}
@Override
public void execute() {
result.success(scripter.confirmAlert(warningCode, 5000));
}
}

View file

@ -1,11 +1,5 @@
package de.jotomo.ruffyscripter.commands; package de.jotomo.ruffyscripter.commands;
import java.util.Collections;
import java.util.List;
import de.jotomo.ruffyscripter.RuffyScripter;
import de.jotomo.ruffy.spi.CommandResult;
public class ReadBasalProfileCommand extends BaseCommand { public class ReadBasalProfileCommand extends BaseCommand {
private final int number; private final int number;
@ -14,17 +8,7 @@ public class ReadBasalProfileCommand extends BaseCommand {
} }
@Override @Override
public CommandResult execute() { public void execute() {
return new CommandResult().success(false).enacted(false); // TODO
}
@Override
public List<String> validateArguments() {
return Collections.emptyList();
}
@Override
public void setScripter(RuffyScripter scripter) {
} }
} }

View file

@ -7,17 +7,19 @@ import org.monkey.d.ruffy.ruffy.driver.display.MenuType;
import org.monkey.d.ruffy.ruffy.driver.display.menu.BolusType; import org.monkey.d.ruffy.ruffy.driver.display.menu.BolusType;
import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuDate; import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuDate;
import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuTime; 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.Date; import java.util.Date;
import java.util.List;
import de.jotomo.ruffy.spi.CommandResult;
import de.jotomo.ruffy.spi.history.Bolus; import de.jotomo.ruffy.spi.history.Bolus;
import de.jotomo.ruffy.spi.history.PumpError;
import de.jotomo.ruffy.spi.history.PumpHistory; import de.jotomo.ruffy.spi.history.PumpHistory;
import de.jotomo.ruffy.spi.history.PumpHistoryRequest; import de.jotomo.ruffy.spi.history.PumpHistoryRequest;
public class ReadHistoryCommand extends BaseCommand { public class ReadHistoryCommand extends BaseCommand {
private static Logger log = LoggerFactory.getLogger(ReadHistoryCommand.class);
private final PumpHistoryRequest request; private final PumpHistoryRequest request;
private final PumpHistory history = new PumpHistory(); private final PumpHistory history = new PumpHistory();
@ -26,24 +28,22 @@ public class ReadHistoryCommand extends BaseCommand {
} }
@Override @Override
public CommandResult execute() { public void execute() {
if (request.reservoirLevel) {
readReservoirLevel();
}
if (request.bolusHistory != PumpHistoryRequest.SKIP if (request.bolusHistory != PumpHistoryRequest.SKIP
|| request.tbrHistory != PumpHistoryRequest.SKIP || request.tbrHistory != PumpHistoryRequest.SKIP
|| request.pumpErrorHistory != PumpHistoryRequest.SKIP || request.pumpErrorHistory != PumpHistoryRequest.SKIP) {
|| request.tddHistory != PumpHistoryRequest.SKIP) {
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();
// TODO see how dana does time mangling for timezones
// bolus history // bolus history
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_DATA); scripter.verifyMenuIsDisplayed(MenuType.BOLUS_DATA);
if (request.bolusHistory != PumpHistoryRequest.SKIP) { if (request.bolusHistory != PumpHistoryRequest.SKIP) {
int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD); int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD);
if (totalRecords > 0) { if (totalRecords > 0) {
if (request.bolusHistory == PumpHistoryRequest.LAST) { if (true || request.bolusHistory == PumpHistoryRequest.LAST) {
Bolus bolus = readBolusRecord(); Bolus bolus = readBolusRecord();
history.bolusHistory.add(bolus); history.bolusHistory.add(bolus);
} else { } else {
@ -56,22 +56,21 @@ public class ReadHistoryCommand extends BaseCommand {
scripter.pressMenuKey(); scripter.pressMenuKey();
scripter.verifyMenuIsDisplayed(MenuType.ERROR_DATA); scripter.verifyMenuIsDisplayed(MenuType.ERROR_DATA);
if (request.pumpErrorHistory != PumpHistoryRequest.SKIP) { if (request.pumpErrorHistory != PumpHistoryRequest.SKIP) {
int code = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.WARNING); int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD);
String message = (String) scripter.getCurrentMenu().getAttribute(MenuAttribute.MESSAGE); if (totalRecords > 0) {
MenuDate date = (MenuDate) scripter.getCurrentMenu().getAttribute(MenuAttribute.DATE); if (true || request.pumpErrorHistory == PumpHistoryRequest.LAST) {
MenuTime time = (MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.TIME); PumpError error = readErrorRecord();
history.pumpErrorHistory.add(error);
} else {
readErrorRecords(request.pumpErrorHistory);
}
}
} }
// tdd history // tdd history
scripter.pressMenuKey(); scripter.pressMenuKey();
scripter.verifyMenuIsDisplayed(MenuType.DAILY_DATA); scripter.verifyMenuIsDisplayed(MenuType.DAILY_DATA);
if (request.tddHistory != PumpHistoryRequest.SKIP) { /*
Double total = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.DAILY_TOTAL);
MenuDate date = (MenuDate) scripter.getCurrentMenu().getAttribute(MenuAttribute.DATE);
MenuTime time = (MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.TIME);
}
// tbr history // tbr history
scripter.pressMenuKey(); scripter.pressMenuKey();
scripter.verifyMenuIsDisplayed(MenuType.TBR_DATA); scripter.verifyMenuIsDisplayed(MenuType.TBR_DATA);
@ -82,64 +81,102 @@ public class ReadHistoryCommand extends BaseCommand {
// TODO start or end time? // TODO start or end time?
MenuTime time = (MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.TIME); MenuTime time = (MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.TIME);
} }
*/
scripter.pressBackKey(); scripter.pressBackKey();
scripter.returnToRootMenu(); scripter.returnToRootMenu();
scripter.verifyRootMenuIsDisplayed(); scripter.verifyRootMenuIsDisplayed();
} }
return new CommandResult().success(true).enacted(false).history(history); if (log.isDebugEnabled()) {
if (!history.bolusHistory.isEmpty()) {
log.debug("Read bolus history:");
for (Bolus bolus : history.bolusHistory) {
log.debug(new Date(bolus.timestamp) + ": " + bolus.toString());
}
}
if (!history.pumpErrorHistory.isEmpty()) {
log.debug("Read error history:");
for (PumpError pumpError : history.pumpErrorHistory) {
log.debug(new Date(pumpError.timestamp) + ": " + pumpError.toString());
}
}
}
result.success(true).history(history);
} }
private void readBolusRecords(long requestedTime) { private void readBolusRecords(long requestedTime) {
int record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD); int record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD);
int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD); int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD);
while (record <= totalRecords) { while (true) {
log.debug("Reading bolus record #" + record + "/" + totalRecords);
Bolus bolus = readBolusRecord(); Bolus bolus = readBolusRecord();
if (requestedTime != PumpHistoryRequest.FULL && bolus.timestamp < requestedTime) { if (requestedTime != PumpHistoryRequest.FULL && bolus.timestamp <= requestedTime) {
break; break;
} }
history.bolusHistory.add(bolus); history.bolusHistory.add(bolus);
scripter.pressDownKey();
scripter.waitForMenuUpdate();
record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD); record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD);
if (record == totalRecords) {
break;
}
} }
} }
@NonNull @NonNull
private Bolus readBolusRecord() { private Bolus readBolusRecord() {
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_DATA);
// Could also be extended, multiwave // Could also be extended, multiwave
BolusType bolusType = (BolusType) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS_TYPE); BolusType bolusType = (BolusType) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS_TYPE);
if (!bolusType.equals(BolusType.NORMAL)) { if (!bolusType.equals(BolusType.NORMAL)) {
throw new CommandException().success(false).enacted(false).message("Unsupported bolus type encountered: " + bolusType); throw new CommandException("Unsupported bolus type encountered: " + bolusType);
} }
// TODO no bolus etc yet? How would that ever look?
Double bolus = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS); Double bolus = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS);
long recordDate = readRecordDate();
return new Bolus(recordDate, bolus);
}
private void readErrorRecords(long requestedTime) {
int record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD);
int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD);
while (true) {
log.debug("Reading error record #" + record + "/" + totalRecords);
PumpError error = readErrorRecord();
if (requestedTime != PumpHistoryRequest.FULL && error.timestamp <= requestedTime) {
break;
}
history.pumpErrorHistory.add(error);
scripter.pressDownKey();
scripter.waitForMenuUpdate();
record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD);
if (record == totalRecords) {
break;
}
}
}
@NonNull
private PumpError readErrorRecord() {
scripter.verifyMenuIsDisplayed(MenuType.ERROR_DATA);
Integer warningCode = (Integer) scripter.getCurrentMenu().getAttribute(MenuAttribute.WARNING);
Integer errorCode = (Integer) scripter.getCurrentMenu().getAttribute(MenuAttribute.ERROR);
String message = (String) scripter.getCurrentMenu().getAttribute(MenuAttribute.MESSAGE);
long recordDate = readRecordDate();
return new PumpError(recordDate, warningCode, errorCode, message);
}
private long readRecordDate() {
MenuDate date = (MenuDate) scripter.getCurrentMenu().getAttribute(MenuAttribute.DATE); MenuDate date = (MenuDate) scripter.getCurrentMenu().getAttribute(MenuAttribute.DATE);
MenuTime time = (MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.TIME); MenuTime time = (MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.TIME);
// TODO handle year changes; if current month == 1 and record date == 12, use $YEAR-1
int currentMonth = new Date().getMonth() + 1; int currentMonth = new Date().getMonth() + 1;
int currentYear = new Date().getYear() + 1900; int currentYear = new Date().getYear() + 1900;
if (currentMonth == 1 && date.getMonth() == 12) { if (currentMonth == 1 && date.getMonth() == 12) {
currentYear -= 1; currentYear -= 1;
} }
long recordDate = new Date(currentYear - 1900, date.getMonth() - 1, date.getDay(), time.getHour(), time.getMinute()).getTime(); return new Date(currentYear - 1900, date.getMonth() - 1, date.getDay(), time.getHour(), time.getMinute()).getTime();
return new Bolus(recordDate, bolus);
}
private void readReservoirLevel() {
scripter.verifyRootMenuIsDisplayed();
scripter.pressCheckKey();
scripter.waitForMenuToBeLeft(MenuType.MAIN_MENU);
scripter.waitForMenuToBeLeft(MenuType.STOP);
scripter.verifyMenuIsDisplayed(MenuType.QUICK_INFO);
int remainingInsulin = ((Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.REMAINING_INSULIN)).intValue();
scripter.returnToRootMenu();
history.reservoirLevel = remainingInsulin;
}
@Override
public List<String> validateArguments() {
return Collections.emptyList();
} }
@Override @Override
@ -149,5 +186,4 @@ public class ReadHistoryCommand extends BaseCommand {
", history=" + history + ", history=" + history +
'}'; '}';
} }
} }

View file

@ -1,25 +1,12 @@
package de.jotomo.ruffyscripter.commands; package de.jotomo.ruffyscripter.commands;
import java.util.Collections;
import java.util.List;
import de.jotomo.ruffy.spi.CommandResult;
import de.jotomo.ruffyscripter.RuffyScripter;
public class ReadPumpStateCommand extends BaseCommand { public class ReadPumpStateCommand extends BaseCommand {
@Override @Override
public CommandResult execute() { public void execute() {
return new CommandResult().success(true).enacted(false).state(scripter.readPumpStateInternal()); // nothing to do, scripter adds state to all command results
result.success = true;
} }
@Override
public List<String> validateArguments() {
return Collections.emptyList();
}
@Override
public void setScripter(RuffyScripter scripter) {}
@Override @Override
public String toString() { public String toString() {
return "ReadPumpStateCommand{}"; return "ReadPumpStateCommand{}";

View file

@ -0,0 +1,56 @@
package de.jotomo.ruffyscripter.commands;
import android.support.annotation.NonNull;
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.BolusType;
import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuDate;
import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuTime;
import java.util.Date;
import de.jotomo.ruffy.spi.history.Bolus;
public class ReadReservoirLevelAndLastBolus extends BaseCommand {
@Override
public void execute() {
scripter.verifyRootMenuIsDisplayed();
scripter.pressCheckKey();
scripter.waitForMenuToBeLeft(MenuType.MAIN_MENU);
scripter.waitForMenuToBeLeft(MenuType.STOP);
scripter.verifyMenuIsDisplayed(MenuType.QUICK_INFO);
result.reservoirLevel = ((Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.REMAINING_INSULIN)).intValue();
scripter.pressCheckKey();
result.lastBolus = readBolusRecord();
scripter.returnToRootMenu();
result.success = true;
}
// TODO deduplicate -> ReadHistoryCommand
@NonNull
private Bolus readBolusRecord() {
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_DATA);
// Could also be extended, multiwave
BolusType bolusType = (BolusType) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS_TYPE);
if (!bolusType.equals(BolusType.NORMAL)) {
throw new CommandException("Unsupported bolus type encountered: " + bolusType);
}
Double bolus = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS);
long recordDate = readRecordDate();
return new Bolus(recordDate, bolus);
}
private long readRecordDate() {
MenuDate date = (MenuDate) scripter.getCurrentMenu().getAttribute(MenuAttribute.DATE);
MenuTime time = (MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.TIME);
int currentMonth = new Date().getMonth() + 1;
int currentYear = new Date().getYear() + 1900;
if (currentMonth == 1 && date.getMonth() == 12) {
currentYear -= 1;
}
return new Date(currentYear - 1900, date.getMonth() - 1, date.getDay(), time.getHour(), time.getMinute()).getTime();
}
}

View file

@ -1,28 +1,29 @@
package de.jotomo.ruffyscripter.commands; package de.jotomo.ruffyscripter.commands;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import de.jotomo.ruffy.spi.BasalProfile; import de.jotomo.ruffy.spi.BasalProfile;
import de.jotomo.ruffyscripter.RuffyScripter;
import de.jotomo.ruffy.spi.CommandResult;
public class SetBasalProfileCommand extends BaseCommand { public class SetBasalProfileCommand extends BaseCommand {
public SetBasalProfileCommand(BasalProfile basalProfile) { private final BasalProfile basalProfile;
public SetBasalProfileCommand(BasalProfile basalProfile) {
this.basalProfile = basalProfile;
} }
@Override @Override
public CommandResult execute() { public void execute() {
return null; // TODO
} }
@Override @Override
public List<String> validateArguments() { public List<String> validateArguments() {
return null; ArrayList<String> violations = new ArrayList<>();
} if (basalProfile == null) {
violations.add("No basal profile supplied");
@Override }
public void setScripter(RuffyScripter scripter) {
return violations;
} }
} }

View file

@ -1,19 +0,0 @@
package de.jotomo.ruffyscripter.commands;
import java.util.List;
import de.jotomo.ruffy.spi.CommandResult;
public class SetBasalRateProfileCommand extends BaseCommand {
@Override
public CommandResult execute() {
// TODO stub
return null;
}
@Override
public List<String> validateArguments() {
// TODO stub
return null;
}
}

View file

@ -12,7 +12,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import de.jotomo.ruffy.spi.CommandResult; import de.jotomo.ruffy.spi.PumpWarningCodes;
public class SetTbrCommand extends BaseCommand { public class SetTbrCommand extends BaseCommand {
private static final Logger log = LoggerFactory.getLogger(SetTbrCommand.class); private static final Logger log = LoggerFactory.getLogger(SetTbrCommand.class);
@ -58,47 +58,42 @@ public class SetTbrCommand extends BaseCommand {
} }
@Override @Override
public CommandResult execute() { public void execute() {
try { boolean cancellingTbr = percentage == 100;
boolean cancellingTbr = percentage == 100;
enterTbrMenu(); enterTbrMenu();
boolean increasingPercentage = inputTbrPercentage(); boolean increasingPercentage = inputTbrPercentage();
verifyDisplayedTbrPercentage(increasingPercentage); verifyDisplayedTbrPercentage(increasingPercentage);
if (cancellingTbr) { if (cancellingTbr) {
cancelTbrAndConfirmCancellationWarning(); cancelTbrAndConfirmCancellationWarning();
} else { } else {
// switch to TBR_DURATION menu by pressing menu key // switch to TBR_DURATION menu by pressing menu key
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); scripter.verifyMenuIsDisplayed(MenuType.TBR_SET);
scripter.pressMenuKey(); scripter.pressMenuKey();
scripter.waitForMenuUpdate(); scripter.waitForMenuUpdate();
scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION); scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION);
boolean increasingDuration = inputTbrDuration(); boolean increasingDuration = inputTbrDuration();
verifyDisplayedTbrDuration(increasingDuration); verifyDisplayedTbrDuration(increasingDuration);
// confirm TBR // confirm TBR
scripter.pressCheckKey(); scripter.pressCheckKey();
scripter.waitForMenuToBeLeft(MenuType.TBR_DURATION); scripter.waitForMenuToBeLeft(MenuType.TBR_DURATION);
} }
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU, scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU,
"Pump did not return to MAIN_MEU after setting TBR. " + "Pump did not return to MAIN_MEU after setting TBR. " +
"Check pump manually, the TBR might not have been set/cancelled."); "Check pump manually, the TBR might not have been set/cancelled.");
// check main menu shows the same values we just set // check main menu shows the same values we just set
if (cancellingTbr) { if (cancellingTbr) {
verifyMainMenuShowsNoActiveTbr(); verifyMainMenuShowsNoActiveTbr();
return new CommandResult().success(true).enacted(true).message("TBR was cancelled"); result.success(true).enacted(true).message("TBR was cancelled");
} else { } else {
verifyMainMenuShowsExpectedTbrActive(); verifyMainMenuShowsExpectedTbrActive();
return new CommandResult().success(true).enacted(true).message( result.success(true).enacted(true)
String.format(Locale.US, "TBR set to %d%% for %d min", percentage, duration)); .message(String.format(Locale.US, "TBR set to %d%% for %d min", percentage, duration));
}
} catch (CommandException e) {
return e.toCommandResult();
} }
} }
@ -146,7 +141,7 @@ public class SetTbrCommand extends BaseCommand {
} }
log.debug("Final displayed TBR percentage: " + displayedPercentage); log.debug("Final displayed TBR percentage: " + displayedPercentage);
if (displayedPercentage != percentage) { if (displayedPercentage != percentage) {
throw new CommandException().message("Failed to set TBR percentage, requested: " throw new CommandException("Failed to set TBR percentage, requested: "
+ percentage + ", actual: " + displayedPercentage); + percentage + ", actual: " + displayedPercentage);
} }
@ -156,7 +151,7 @@ public class SetTbrCommand extends BaseCommand {
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); scripter.verifyMenuIsDisplayed(MenuType.TBR_SET);
long refreshedDisplayedTbrPecentage = readDisplayedPercentage(); long refreshedDisplayedTbrPecentage = readDisplayedPercentage();
if (displayedPercentage != refreshedDisplayedTbrPecentage) { if (displayedPercentage != refreshedDisplayedTbrPecentage) {
throw new CommandException().message("Failed to set TBR percentage: " + throw new CommandException("Failed to set TBR percentage: " +
"percentage changed after input stopped from " "percentage changed after input stopped from "
+ displayedPercentage + " -> " + refreshedDisplayedTbrPecentage); + displayedPercentage + " -> " + refreshedDisplayedTbrPecentage);
} }
@ -208,7 +203,7 @@ public class SetTbrCommand extends BaseCommand {
log.debug("Final displayed TBR duration: " + displayedDuration); log.debug("Final displayed TBR duration: " + displayedDuration);
if (displayedDuration != duration) { if (displayedDuration != duration) {
throw new CommandException().message("Failed to set TBR duration, requested: " throw new CommandException("Failed to set TBR duration, requested: "
+ duration + ", actual: " + displayedDuration); + duration + ", actual: " + displayedDuration);
} }
@ -218,7 +213,7 @@ public class SetTbrCommand extends BaseCommand {
scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION); scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION);
long refreshedDisplayedTbrDuration = readDisplayedDuration(); long refreshedDisplayedTbrDuration = readDisplayedDuration();
if (displayedDuration != refreshedDisplayedTbrDuration) { if (displayedDuration != refreshedDisplayedTbrDuration) {
throw new CommandException().message("Failed to set TBR duration: " + throw new CommandException("Failed to set TBR duration: " +
"duration changed after input stopped from " "duration changed after input stopped from "
+ displayedDuration + " -> " + refreshedDisplayedTbrDuration); + displayedDuration + " -> " + refreshedDisplayedTbrDuration);
} }
@ -234,7 +229,7 @@ public class SetTbrCommand extends BaseCommand {
// We could read the remaining duration from MAIN_MENU, but by the time we're here, // We could read the remaining duration from MAIN_MENU, but by the time we're here,
// the pump could have moved from 0:02 to 0:01, so instead, check if a "TBR CANCELLED" alert // the pump could have moved from 0:02 to 0:01, so instead, check if a "TBR CANCELLED" alert
// is raised and if so dismiss it // is raised and if so dismiss it
scripter.confirmAlert("TBR CANCELLED", 5000); scripter.confirmAlert(PumpWarningCodes.TBR_CANCELLED, 2000);
} }
private void verifyMainMenuShowsNoActiveTbr() { private void verifyMainMenuShowsNoActiveTbr() {
@ -242,7 +237,7 @@ public class SetTbrCommand extends BaseCommand {
Double tbrPercentage = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.TBR); Double tbrPercentage = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.TBR);
boolean runtimeDisplayed = scripter.getCurrentMenu().attributes().contains(MenuAttribute.RUNTIME); boolean runtimeDisplayed = scripter.getCurrentMenu().attributes().contains(MenuAttribute.RUNTIME);
if (tbrPercentage != 100 || runtimeDisplayed) { if (tbrPercentage != 100 || runtimeDisplayed) {
throw new CommandException().message("Cancelling TBR failed, TBR is still set according to MAIN_MENU"); throw new CommandException("Cancelling TBR failed, TBR is still set according to MAIN_MENU");
} }
} }
@ -251,7 +246,7 @@ public class SetTbrCommand extends BaseCommand {
// new TBR set; percentage and duration must be displayed ... // new TBR set; percentage and duration must be displayed ...
if (!scripter.getCurrentMenu().attributes().contains(MenuAttribute.TBR) || if (!scripter.getCurrentMenu().attributes().contains(MenuAttribute.TBR) ||
!scripter.getCurrentMenu().attributes().contains(MenuAttribute.RUNTIME)) { !scripter.getCurrentMenu().attributes().contains(MenuAttribute.RUNTIME)) {
throw new CommandException().message("Setting TBR failed, according to MAIN_MENU no TBR is active"); throw new CommandException("Setting TBR failed, according to MAIN_MENU no TBR is active");
} }
Double mmTbrPercentage = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.TBR); Double mmTbrPercentage = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.TBR);
MenuTime mmTbrDuration = (MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.RUNTIME); MenuTime mmTbrDuration = (MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.RUNTIME);
@ -260,7 +255,7 @@ public class SetTbrCommand extends BaseCommand {
// 29 minutes and 59 seconds, so that 29 minutes are displayed // 29 minutes and 59 seconds, so that 29 minutes are displayed
int mmTbrDurationInMinutes = mmTbrDuration.getHour() * 60 + mmTbrDuration.getMinute(); int mmTbrDurationInMinutes = mmTbrDuration.getHour() * 60 + mmTbrDuration.getMinute();
if (mmTbrPercentage != percentage || (mmTbrDurationInMinutes != duration && mmTbrDurationInMinutes + 1 != duration)) { if (mmTbrPercentage != percentage || (mmTbrDurationInMinutes != duration && mmTbrDurationInMinutes + 1 != duration)) {
throw new CommandException().message("Setting TBR failed, TBR in MAIN_MENU differs from expected"); throw new CommandException("Setting TBR failed, TBR in MAIN_MENU differs from expected");
} }
} }

View file

@ -1,50 +0,0 @@
package de.jotomo.ruffyscripter.commands;
import org.monkey.d.ruffy.ruffy.driver.display.MenuAttribute;
import org.monkey.d.ruffy.ruffy.driver.display.MenuType;
import java.util.Collections;
import java.util.List;
import de.jotomo.ruffy.spi.CommandResult;
import de.jotomo.ruffy.spi.PumpState;
import de.jotomo.ruffy.spi.history.PumpError;
// TODO rename to ConfirmALarm(alarm) => logic in CP, just report back alarm, then explicitely confirm that one alarm.
// multiple alarms oncy occur for errors (battery/cartdige low/occlusion) => let ring.
public class TakeOverAlarmsCommand extends BaseCommand {
@Override
public CommandResult execute() {
if (scripter.getCurrentMenu().getType() != MenuType.WARNING_OR_ERROR) {
return new CommandResult().success(false).enacted(false).message("No alarm active on the pump");
}
while (scripter.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
new PumpError(System.currentTimeMillis(),
"",
// TODO
// codes unqiue across W/E?
// (int) currentMenu.getAttribute(MenuAttribute.WARNING),
// (int) currentMenu.getAttribute(MenuAttribute.ERROR),
(String) scripter.getCurrentMenu().getAttribute(MenuAttribute.MESSAGE));
}
// confirm alert
scripter.verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR);
scripter.pressCheckKey();
// dismiss alert
scripter.verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR);
scripter.pressCheckKey();
scripter.waitForMenuToBeLeft(MenuType.WARNING_OR_ERROR);
PumpState pumpState = scripter.readPumpStateInternal();
return new CommandResult()
.success(true)
.enacted(false /* well, no treatments were enacted ... */)
// .message(pumpState.errorMsg) // todo yikes?
.state(pumpState);
}
@Override
public List<String> validateArguments() {
return Collections.emptyList();
}
}