* 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.LoggerFactory;
import java.util.List;
import de.jotomo.ruffy.spi.CommandResult;
import de.jotomo.ruffy.spi.PumpState;
import de.jotomo.ruffy.spi.history.Bolus;
@ -62,20 +60,17 @@ public class ComboFragment extends SubscriberFragment implements View.OnClickLis
public void onClick(View view) {
switch (view.getId()) {
case R.id.combo_refresh:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
ComboPlugin.getPlugin().refreshDataFromPump("User request");
}
});
Thread thread = new Thread(() -> ComboPlugin.getPlugin().refreshDataFromPump("User request"));
thread.start();
break;
case R.id.combo_error_history:
// TODO show popup with pump errors and comm problems
break;
case R.id.combo_stats:
// case R.id.combo_stats:
// 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,21 +80,13 @@ public class ComboFragment extends SubscriberFragment implements View.OnClickLis
}
public void updateGUI() {
Activity activity = getActivity();
if (activity != null)
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Activity fragmentActivity = getActivity();
if (fragmentActivity != null)
fragmentActivity.runOnUiThread(() -> {
ComboPlugin plugin = ComboPlugin.getPlugin();
// activity
String activity = plugin.getPump().activity;
activityView.setText(activity != null ? activity : getString(R.string.combo_action_idle));
if (plugin.isInitialized()) {
// state
stateView.setText(plugin.getStateSummary());
PumpState ps = plugin.getPump().state;
if (plugin.getPump().state.errorMsg != null
|| ps.insulinState == PumpState.EMPTY
@ -111,6 +98,11 @@ public class ComboFragment extends SubscriberFragment implements View.OnClickLis
stateView.setTextColor(Color.WHITE);
}
// activity
String activity = plugin.getPump().activity;
activityView.setText(activity != null ? activity : getString(R.string.combo_action_idle));
if (plugin.isInitialized()) {
// battery
if (ps.batteryState == PumpState.EMPTY) {
batteryView.setText("{fa-battery-empty}");
@ -118,6 +110,9 @@ public class ComboFragment extends SubscriberFragment implements View.OnClickLis
} else if (ps.batteryState == PumpState.LOW) {
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);
@ -137,8 +132,8 @@ public class ComboFragment extends SubscriberFragment implements View.OnClickLis
// last connection
CommandResult lastCmdResult = plugin.getPump().lastCmdResult;
if (lastCmdResult != null) {
String minAgo = DateUtil.minAgo(lastCmdResult.completionTime);
String time = DateUtil.timeString(lastCmdResult.completionTime);
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);
@ -152,9 +147,8 @@ public class ComboFragment extends SubscriberFragment implements View.OnClickLis
}
// 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);
Bolus bolus = plugin.getPump().lastBolus;
if (bolus != null && bolus.timestamp + 6 * 60 * 60 * 1000 >= System.currentTimeMillis()) {
long agoMsc = System.currentTimeMillis() - bolus.timestamp;
double agoHours = agoMsc / 60d / 60d / 1000d;
lastBolusView.setText(getString(R.string.combo_last_bolus,
@ -170,7 +164,7 @@ public class ComboFragment extends SubscriberFragment implements View.OnClickLis
boolean tbrActive = ps.tbrPercent != -1 && ps.tbrPercent != 100;
String tbrStr = "";
if (tbrActive) {
long minSinceRead = (System.currentTimeMillis() - lastCmdResult.completionTime) / 1000 / 60;
long minSinceRead = (System.currentTimeMillis() - plugin.getPump().state.timestamp) / 1000 / 60;
long remaining = ps.tbrRemainingDuration - minSinceRead;
if (remaining >= 0) {
tbrStr = getString(R.string.combo_tbr_remaining, ps.tbrPercent, remaining);
@ -179,7 +173,6 @@ public class ComboFragment extends SubscriberFragment implements View.OnClickLis
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.PumpCombo.events.EventComboPumpUpdateGUI;
import info.nightscout.utils.DateUtil;
import info.nightscout.utils.DecimalFormatter;
/**
* Created by mike on 05.08.2016.
@ -51,7 +52,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
private final RuffyCommands ruffyScripter;
// 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;
@ -128,12 +129,12 @@ public class ComboPlugin implements PluginBase, PumpInterface {
String getStateSummary() {
PumpState ps = pump.state;
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))
return MainApp.sResources.getString(R.string.combo_pump_state_suspended_due_to_error);
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_running);
return MainApp.sResources.getString(R.string.combo_pump_state_normal);
}
@Override
@ -207,41 +208,46 @@ public class ComboPlugin implements PluginBase, PumpInterface {
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
public synchronized void refreshDataFromPump(String reason) {
log.debug("RefreshDataFromPump called");
if (!pump.initialized) {
runCommand(MainApp.sResources.getString(R.string.connecting), new CommandExecution() {
@Override
public CommandResult execute() {
return ruffyScripter.readPumpState();
// TODO reading profile
long maxWait = System.currentTimeMillis() + 15 * 1000;
while (!ruffyScripter.isPumpAvailable()) {
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;
} 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() {
CommandResult commandResult = runCommand("Checking pump history", false, new CommandExecution() {
@Override
public CommandResult execute() {
return ruffyScripter.readHistory(
CommandResult commandResult = runCommand(MainApp.sResources.getString(R.string.combo_pump_action_checking_history), () ->
ruffyScripter.readHistory(
new PumpHistoryRequest()
.reservoirLevel(true)
.bolusHistory(PumpHistoryRequest.LAST)
.tbrHistory(PumpHistoryRequest.LAST)
.errorHistory(PumpHistoryRequest.LAST));
}
});
.errorHistory(PumpHistoryRequest.LAST)));
if (!commandResult.success || commandResult.history == null) {
// TODO error case, command
@ -250,6 +256,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
// TODO opt, construct PumpHistoryRequest to requset only what needs updating
boolean syncNeeded = false;
PumpHistoryRequest request = new PumpHistoryRequest();
// last bolus
List<Treatment> treatments = MainApp.getConfigBuilder().getTreatmentsFromHistory();
@ -267,11 +274,10 @@ public class ComboPlugin implements PluginBase, PumpInterface {
pumpBolus = bolusHistory.get(0);
}
if (aapsBolus == null || pumpBolus == null) {
syncNeeded = true;
} else if (Math.abs(aapsBolus.insulin - pumpBolus.amount) > 0.05
|| aapsBolus.date != pumpBolus.timestamp) {
if ((aapsBolus == null || pumpBolus == null)
|| (Math.abs(aapsBolus.insulin - pumpBolus.amount) > 0.05 || aapsBolus.date != pumpBolus.timestamp)) {
syncNeeded = true;
request.bolusHistory = PumpHistoryRequest.FULL;
}
// last tbr
@ -282,49 +288,56 @@ public class ComboPlugin implements PluginBase, PumpInterface {
}
Tbr pumpTbr = null;
List<Tbr> tbrHistory = commandResult.history.tbrHistory;
if(!tbrHistory.isEmpty()) {
if (!tbrHistory.isEmpty()) {
pumpTbr = tbrHistory.get(0);
}
if (aapsTbr == null || pumpTbr == null) {
syncNeeded = true;
} else if (aapsTbr.percentRate != pumpTbr.percent || aapsTbr.durationInMinutes != pumpTbr.duration) {
if ((aapsTbr == null || pumpTbr == null)
|| (aapsTbr.percentRate != pumpTbr.percent || aapsTbr.durationInMinutes != pumpTbr.duration)) {
syncNeeded = true;
request.tbrHistory = PumpHistoryRequest.FULL;
}
// 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) {
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
// this get's called multiple times a minute, must absolutely be cached
@Override
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();
Double basal = profile.getBasal();
log.trace("getBaseBasalrate returning " + basal);
return basal;
}
private static BolusProgressReporter nullBolusProgressReporter = new BolusProgressReporter() {
@Override
public void report(State state, int percent, double delivered) {}
private static BolusProgressReporter nullBolusProgressReporter = (state, percent, delivered) -> {
};
private static BolusProgressReporter bolusProgressReporter =
new BolusProgressReporter() {
@Override
public void report(BolusProgressReporter.State state, int percent, double delivered) {
private static BolusProgressReporter bolusProgressReporter = (state, percent, delivered) -> {
EventOverviewBolusProgress event = EventOverviewBolusProgress.getInstance();
switch (state) {
case PROGRAMMING:
@ -348,7 +361,6 @@ public class ComboPlugin implements PluginBase, PumpInterface {
}
event.percent = percent;
MainApp.bus().post(event);
}
};
/**
@ -356,17 +368,23 @@ public class ComboPlugin implements PluginBase, PumpInterface {
*/
@Override
public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) {
// TODO for non-SMB: read resorvoir level first to make sure there's enough insulin left
try {
if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) {
if (detailedBolusInfo.insulin > 0) {
if (detailedBolusInfo.insulin == 0 && detailedBolusInfo.carbs == 0) {
// neither carbs nor bolus requested
PumpEnactResult pumpEnactResult = new PumpEnactResult();
pumpEnactResult.success = false;
pumpEnactResult.enacted = false;
pumpEnactResult.bolusDelivered = 0d;
pumpEnactResult.carbsDelivered = 0d;
pumpEnactResult.comment = MainApp.instance().getString(R.string.danar_invalidinput);
log.error("deliverTreatment: Invalid input");
return pumpEnactResult;
} else 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;
@ -381,17 +399,6 @@ public class ComboPlugin implements PluginBase, PumpInterface {
MainApp.bus().post(bolusingEvent);
return pumpEnactResult;
}
} else {
// neither carbs nor bolus requested
PumpEnactResult pumpEnactResult = new PumpEnactResult();
pumpEnactResult.success = false;
pumpEnactResult.enacted = false;
pumpEnactResult.bolusDelivered = 0d;
pumpEnactResult.carbsDelivered = 0d;
pumpEnactResult.comment = MainApp.instance().getString(R.string.danar_invalidinput);
log.error("deliverTreatment: Invalid input");
return pumpEnactResult;
}
} finally {
MainApp.bus().post(new EventComboPumpUpdateGUI());
}
@ -399,18 +406,18 @@ public class ComboPlugin implements PluginBase, PumpInterface {
@NonNull
private PumpEnactResult deliverBolus(final DetailedBolusInfo detailedBolusInfo) {
CommandResult bolusCmdResult = runCommand(MainApp.sResources.getString(R.string.combo_action_bolusing), new CommandExecution() {
@Override
public CommandResult execute() {
return ruffyScripter.deliverBolus(detailedBolusInfo.insulin,
detailedBolusInfo.isSMB ? nullBolusProgressReporter : bolusProgressReporter);
}
});
// TODO
// before non-SMB: check enough insulin is available, check we're up to date on boluses
// after bolus: update reservoir level and check the bolus we just did is actually there
// 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.success = bolusCmdResult.success;
pumpEnactResult.enacted = bolusCmdResult.enacted;
pumpEnactResult.comment = bolusCmdResult.message;
// pumpEnactResult.comment = bolusCmdResult.message;
// if enacted, add bolus and carbs to treatment history
if (pumpEnactResult.enacted) {
@ -421,7 +428,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
pumpEnactResult.bolusDelivered = detailedBolusInfo.insulin;
pumpEnactResult.carbsDelivered = detailedBolusInfo.carbs;
detailedBolusInfo.date = bolusCmdResult.completionTime;
detailedBolusInfo.date = System.currentTimeMillis();
MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo);
} else {
pumpEnactResult.bolusDelivered = 0d;
@ -432,6 +439,8 @@ public class ComboPlugin implements PluginBase, PumpInterface {
@Override
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();
}
@ -471,16 +480,11 @@ public class ComboPlugin implements PluginBase, PumpInterface {
}
final int finalAdjustedPercent = adjustedPercent;
CommandResult commandResult = runCommand(MainApp.sResources.getString(R.string.combo_action_setting_tbr), new CommandExecution() {
@Override
public CommandResult execute() {
return ruffyScripter.setTbr(finalAdjustedPercent, durationInMinutes);
}
}
);
CommandResult commandResult = runCommand(MainApp.sResources.getString(R.string.combo_action_setting_tbr), () -> ruffyScripter.setTbr(finalAdjustedPercent, durationInMinutes));
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 ...
// 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?
@ -496,7 +500,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
PumpEnactResult pumpEnactResult = new PumpEnactResult();
pumpEnactResult.success = commandResult.success;
pumpEnactResult.enacted = commandResult.enacted;
pumpEnactResult.comment = commandResult.message;
// pumpEnactResult.comment = commandResult.message;
pumpEnactResult.isPercent = true;
// Combo would have bailed if this wasn't set properly. Maybe we should
// have the command return this anyways ...
@ -524,21 +528,16 @@ public class ComboPlugin implements PluginBase, PumpInterface {
if (activeTemp == null || userRequested) {
/* v1 compatibility to sync DB to pump if they diverged (activeTemp == null) */
log.debug("cancelTempBasal: hard-cancelling TBR since user requested");
commandResult = runCommand(MainApp.sResources.getString(R.string.combo_action_cancelling_tbr), new CommandExecution() {
@Override
public CommandResult execute() {
return ruffyScripter.cancelTbr();
}
});
commandResult = runCommand(MainApp.sResources.getString(R.string.combo_action_cancelling_tbr), ruffyScripter::cancelTbr);
if (commandResult.enacted) {
tempBasal = new TemporaryBasal(commandResult.completionTime);
tempBasal = new TemporaryBasal(System.currentTimeMillis());
tempBasal.durationInMinutes = 0;
tempBasal.source = Source.USER;
pumpEnactResult.isTempCancel = true;
}
} 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.");
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;
@ -552,15 +551,10 @@ public class ComboPlugin implements PluginBase, PumpInterface {
// on whether the TBR we're cancelling is above or below 100%.
final int percentage = (activeTemp.percentRate > 100) ? 110 : 90;
log.debug("cancelTempBasal: changing TBR to " + percentage + "% for 15 mins.");
commandResult = runCommand(MainApp.sResources.getString(R.string.combo_action_setting_tbr), new CommandExecution() {
@Override
public CommandResult execute() {
return ruffyScripter.setTbr(percentage, 15);
}
});
commandResult = runCommand(MainApp.sResources.getString(R.string.combo_action_setting_tbr), () -> ruffyScripter.setTbr(percentage, 15));
if (commandResult.enacted) {
tempBasal = new TemporaryBasal(commandResult.completionTime);
tempBasal = new TemporaryBasal(System.currentTimeMillis());
tempBasal.durationInMinutes = 15;
tempBasal.source = Source.USER;
tempBasal.percentRate = percentage;
@ -576,7 +570,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
if (commandResult != null) {
pumpEnactResult.success = commandResult.success;
pumpEnactResult.enacted = commandResult.enacted;
pumpEnactResult.comment = commandResult.message;
// pumpEnactResult.comment = commandResult.message;
}
return pumpEnactResult;
}
@ -585,23 +579,47 @@ public class ComboPlugin implements PluginBase, PumpInterface {
CommandResult execute();
}
private CommandResult runCommand(String status, CommandExecution commandExecution) {
return runCommand(status, true, commandExecution);
// TODO if there was an error (or the pump was suspended) force a resync before a bolus;
// transport a message, e.g. 'new bolus found on pump, synced, check and issue bolus again'
// back to the user?b
}
private CommandResult runCommand(String activity, boolean checkTbrMisMatch, CommandExecution commandExecution) {
private synchronized CommandResult runCommand(String activity, CommandExecution commandExecution) {
if (activity != null) {
pump.activity = activity;
MainApp.bus().post(new EventComboPumpUpdateGUI());
}
// CommandResult precheck = ruffyScripter.readPumpState();
// tbrcheck?
// 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();
pump.lastCmdResult = commandResult;
pump.lastConnectionAttempt = System.currentTimeMillis();
if (commandResult.success) {
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?
// 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
@ -627,105 +645,76 @@ public class ComboPlugin implements PluginBase, PumpInterface {
commandResult.state = takeOverAlarmResult.state;
}*/
pump.lastCmdResult = commandResult;
pump.state = commandResult.state;
// TODO call this explicitely when needed after/before calling this?
if (checkTbrMisMatch) {
checkForTbrMismatch();
}
// if (checkTbrMisMatch) {
// 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
if (activity != null) {
pump.activity = null;
MainApp.bus().post(new EventComboPumpUpdateGUI());
}
return commandResult;
}
// TODO rename to checkState or so and also check time (& date) of pump
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 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());
// if (true) {
//
// // not yet
// } else
boolean sync = false;
if (aapsTbr == null && pump.state.tbrActive) {
// pump runs TBR AAPS is unaware off
// => fetch full history so the full TBR is added to treatments
log.debug("JOE: sync required 1");
runFullSync();
log.debug("Pump runs TBR AAPS is unaware of, reading last 3h of pump TBR history");
sync = true;
} else if (aapsTbr != null && !pump.state.tbrActive) {
// AAPS has a TBR but the pump isn't running a TBR
// => remove the TBR from treatments
// => 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");
log.debug("AAPS shows TBR but pump isn't running a TBR; deleting TBR in AAPS and reading last 3h of pump TBR history");
MainApp.getDbHelper().delete(aapsTbr);
runFullSync();
sync = true;
} else if (aapsTbr != null && pump.state.tbrActive) {
// both AAPS and pump have a TBR ...
if (aapsTbr.percentRate != pump.state.tbrPercent) {
// ... but they have different percentages
// => remove TBR from treatments
// => full history sync so we get up to date on actual IOB
log.debug("JOE: sync required 3");
log.debug("TBR percentage differs between AAPS and pump; deleting TBR in AAPS and reading last 3h of pump TBR history");
MainApp.getDbHelper().delete(aapsTbr);
runFullSync();
sync = true;
}
int durationDiff = Math.abs(aapsTbr.getPlannedRemainingMinutes() - pump.state.tbrRemainingDuration);
if (durationDiff > 2) {
// ... but they have different runtimes
// ^ same as above, merge branches
log.debug("JOE: sync required 4");
log.debug("TBR duration differs between AAPS and pump; deleting TBR in AAPS and reading last 3h of pump TBR history");
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?
// could take 15m or so if there are missed SGVs ...
// new sensitivity calc required, no?
}
private void runFullSync() {
// TODO separate fetching and comparing
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)
);
}
});
private void runFullSync(final PumpHistoryRequest request) {
CommandResult result = runCommand("Syncing full pump history", () -> ruffyScripter.readHistory(request));
// boluses
// TBRs
// errors
// TODO
}
@ -734,29 +723,30 @@ public class ComboPlugin implements PluginBase, PumpInterface {
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
public JSONObject getJSONStatus() {
CommandResult lastCmdResult = pump.lastCmdResult;
if (lastCmdResult == null || lastCmdResult.completionTime + 5 * 60 * 1000L < System.currentTimeMillis()) {
if (!pump.initialized) {
return null;
}
try {
JSONObject pumpJson = new JSONObject();
JSONObject statusJson = new JSONObject();
JSONObject extendedJson = new JSONObject();
statusJson.put("status", getStateSummary());
extendedJson.put("Version", BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION);
try {
extendedJson.put("ActiveProfile", MainApp.getConfigBuilder().getProfileName());
} catch (Exception e) {
}
statusJson.put("timestamp", lastCmdResult.completionTime);
pumpJson.put("clock", DateUtil.toISOString(pump.lastSuccessfulConnection));
pumpJson.put("reservoir", pump.reservoirLevel);
JSONObject statusJson = new JSONObject();
statusJson.put("status", getStateSummary());
statusJson.put("timestamp", pump.lastSuccessfulConnection);
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 != null) {
if (ps.tbrActive) {
extendedJson.put("TempBasalAbsoluteRate", ps.tbrRate);
extendedJson.put("TempBasalPercent", ps.tbrPercent);
@ -765,13 +755,23 @@ public class ComboPlugin implements PluginBase, PumpInterface {
if (ps.errorMsg != null) {
extendedJson.put("ErrorMessage", ps.errorMsg);
}
}
// more info here .... look at dana plugin
pumpJson.put("status", statusJson);
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;
} catch (Exception e) {
@ -781,10 +781,8 @@ public class ComboPlugin implements PluginBase, PumpInterface {
return null;
}
// TODO
@Override
public String deviceID() {
// Serial number here
return "Combo";
}
@ -795,7 +793,6 @@ public class ComboPlugin implements PluginBase, PumpInterface {
@Override
public String shortStatus(boolean veryShort) {
// TODO trim for wear if veryShort==true
return getStateSummary();
}

View file

@ -3,24 +3,31 @@ package info.nightscout.androidaps.plugins.PumpCombo;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import de.jotomo.ruffy.spi.BasalProfile;
import de.jotomo.ruffy.spi.PumpState;
import de.jotomo.ruffy.spi.CommandResult;
import de.jotomo.ruffy.spi.history.Bolus;
import de.jotomo.ruffy.spi.history.PumpHistory;
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;
// TODO actually ... this isn't about successful command execution, but whether we could connect to the pump at all
volatile long lastSuccessfulConnection;
volatile long lastConnectionAttempt;
@Nullable
volatile CommandResult lastCmdResult;
public volatile String activity;
@NonNull
volatile PumpState state = new PumpState();
volatile int reservoirLevel = -1;
volatile Bolus lastBolus = null;
@Nullable
volatile BasalProfile basalProfile;
@NonNull
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() {
final PumpInterface pump = MainApp.getConfigBuilder();
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();
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
// a simple 'Enable/disable alarms' button on the actions tab?
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;
MainApp.bus().post(new EventNewNotification(n));
} else if (SP.getBoolean("syncprofiletopump", false) && !pump.isThisProfileSet(profile)) {

View file

@ -95,7 +95,7 @@
android:gravity="start"
android:paddingLeft="5dp"
android:textColor="@android:color/white"
android:textSize="20dp" />
android:textSize="20sp" />
</LinearLayout>
@ -360,19 +360,6 @@
android:paddingRight="0dp"
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
android:id="@+id/combo_error_history"
style="@style/ButtonSmallFontStyle"

View file

@ -684,13 +684,12 @@
<string name="pump_errors_history">Fehlerprotokol</string>
<string name="treatments_wizard_tt_label">TZ</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_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_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_setting_tbr">TBR wird gesetzt</string>
<string name="combo_action_bolusing">Bolus wird abgegeben</string>

View file

@ -748,7 +748,6 @@
<string name="reuse">reuse</string>
<string name="wearcontrol_title">Controls from 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="active"><![CDATA[<Active>]]></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_last_connection_time">%s (%s)</string>
<string name="combo_tbr_remaining">%d%% (%d remaining)</string>
<string name="combo_last_bolus">%.2f U (%.1f %s, %s)</string>
<string name="combo_pump_state_unreachable">Pump unreachable</string>
<string name="combo_last_bolus">%.1f U (%.1f %s, %s)</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_by_user">Suspended by user</string>
<string name="combo_pump_state_running">Running</string>
<string name="combo_action_refreshing">Refreshing</string>
<string name="combo_pump_state_normal">Normal</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_setting_tbr">Setting TBR</string>
<string name="combo_action_bolusing">Bolusing</string>

View file

@ -1,38 +1,43 @@
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;
public class CommandResult {
/** The request made made to the pump, like setting a TBR. */
public String request;
/** Whether the command was executed successfully. */
public boolean success;
/** 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. */
public boolean enacted;
/** Time the command completed. */
public long completionTime;
/** Null unless an unhandled exception was raised. */
public Exception exception;
/** (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. */
public PumpState state;
/** History if requested by the command. */
public PumpHistory history;
/** Basal rate profile if requested. */
public List<BasalProfile> basalProfiles;
public BasalProfile basalProfile;
/** Total duration the command took. */
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) {
this.request = request;
return this;
public int reservoirLevel = -1;
public Bolus lastBolus;
public long pumpTime;
public CommandResult() {
}
public CommandResult success(boolean success) {
@ -45,11 +50,6 @@ public class CommandResult {
return this;
}
public CommandResult completionTime(long completionTime) {
this.completionTime = completionTime;
return this;
}
public CommandResult duration(String duration) {
this.duration = duration;
return this;
@ -60,10 +60,10 @@ public class CommandResult {
return this;
}
public CommandResult message(String message) {
this.message = message;
return this;
}
// public CommandResult message(String message) {
// this.message = message;
// return this;
// }
public CommandResult state(PumpState state) {
this.state = state;
@ -75,24 +75,24 @@ public class CommandResult {
return this;
}
public CommandResult basalProfile(List<BasalProfile> basalProfiles) {
this.basalProfiles = basalProfiles;
public CommandResult basalProfile(BasalProfile basalProfile) {
this.basalProfile = basalProfile;
return this;
}
@Override
public String toString() {
return "CommandResult{" +
"request='" + request + '\'' +
", success=" + success +
", enacted=" + enacted +
", completionTime=" + completionTime +
", exception=" + exception +
", message='" + message + '\'' +
// ", message='" + message + '\'' +
", state=" + state +
", history=" + history +
", basalProfiles=" + basalProfiles +
", basalProfile=" + basalProfile +
", 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. */
public class PumpState {
public long timestamp;
public String menu = null;
public boolean tbrActive = false;
/** TBR percentage. 100% means no TBR active, just the normal basal rate running. */
@ -22,10 +23,11 @@ public class PumpState {
public String errorMsg;
public boolean suspended;
public static final int UNKNOWN = -1;
public static final int LOW = 1;
public static final int EMPTY = 2;
public int batteryState = - 1;
public int insulinState = -1;
public int batteryState = UNKNOWN;
public int insulinState = UNKNOWN;
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();
// TODO read Dana code wrt to syncing and such
/** 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?
*/
CommandResult confirmAlert(int warningCode);
boolean isPumpAvailable();
boolean isPumpBusy();
// start everything with this: read pump state.
// see if there's an error active.
CommandResult readPumpState();
CommandResult readReservoirLevelAndLastBolus();
CommandResult readHistory(PumpHistoryRequest request);
CommandResult readBasalProfile(int number);
CommandResult setBasalProfile(BasalProfile basalProfile);
CommandResult getDateAndTime();
CommandResult setDateAndTime(Date date);
void requestPairing();

View file

@ -7,4 +7,12 @@ public class Bolus extends HistoryRecord {
super(timestamp);
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 {
/** 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. */
public final String message;
public PumpError(long timestamp, String code, String message) {
public PumpError(long timestamp, Integer warningCode, Integer errorCode, String message) {
super(timestamp);
this.code = code;
this.warningCode = warningCode;
this.errorCode = errorCode;
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 java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class PumpHistory {
public int reservoirLevel = -1;
@NonNull
public List<Bolus> bolusHistory = new ArrayList<>();
@NonNull
@ -16,12 +16,6 @@ public class PumpHistory {
@NonNull
public List<Tdd> tddHistory = new ArrayList<>();
public PumpHistory reservoirLevel(int reservoirLevel) {
this.reservoirLevel = reservoirLevel
;
return this;
}
public PumpHistory bolusHistory(List<Bolus> bolusHistory) {
this.bolusHistory = bolusHistory;
return this;
@ -32,7 +26,7 @@ public class PumpHistory {
return this;
}
public PumpHistory errorHistory(List<PumpError> pumpErrorHistory) {
public PumpHistory pumpErrorHistory(List<PumpError> pumpErrorHistory) {
this.pumpErrorHistory = pumpErrorHistory;
return this;
}
@ -45,7 +39,6 @@ public class PumpHistory {
@Override
public String toString() {
return "PumpHistory{" +
"reservoirLevel=" + reservoirLevel +
", bolusHistory=" + bolusHistory.size() +
", tbrHistory=" + tbrHistory.size() +
", pumpErrorHistory=" + pumpErrorHistory.size() +

View file

@ -2,8 +2,6 @@ package de.jotomo.ruffy.spi.history;
/** What data a 'read history' request should return. */
public class PumpHistoryRequest {
public boolean reservoirLevel;
/* History to read:
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.
@ -15,12 +13,6 @@ public class PumpHistoryRequest {
public long bolusHistory = SKIP;
public long tbrHistory = SKIP;
public long pumpErrorHistory = SKIP;
public long tddHistory = SKIP;
public PumpHistoryRequest reservoirLevel(boolean reservoirLevel) {
this.reservoirLevel = reservoirLevel;
return this;
}
public PumpHistoryRequest bolusHistory(long bolusHistory) {
this.bolusHistory = bolusHistory;
@ -37,19 +29,12 @@ public class PumpHistoryRequest {
return this;
}
public PumpHistoryRequest tddHistory(long tddHistory) {
this.tddHistory = tddHistory;
return this;
}
@Override
public String toString() {
return "PumpHistoryRequest{" +
"reservoirLevel=" + reservoirLevel +
", bolusHistory=" + bolusHistory +
", tbrHistory=" + tbrHistory +
", pumpErrorHistory=" + pumpErrorHistory +
", tddHistory=" + tddHistory +
'}';
}
}

View file

@ -9,4 +9,13 @@ public class Tbr extends HistoryRecord {
this.duration = duration;
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);
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;
}
private RuffyCommandsV1Impl() {
private RuffyCommandsV1Impl() {}
@Override
public CommandResult getDateAndTime() {
return delegate.getDateAndTime();
}
@Override
public CommandResult takeOverAlarms() {
return delegate.takeOverAlarms();
public CommandResult readReservoirLevelAndLastBolus() {
return delegate.readReservoirLevelAndLastBolus();
}
@Override
public CommandResult confirmAlert(int warningCode) {
return delegate.confirmAlert(warningCode);
}
@Override

View file

@ -34,12 +34,13 @@ import de.jotomo.ruffyscripter.commands.BolusCommand;
import de.jotomo.ruffyscripter.commands.CancelTbrCommand;
import de.jotomo.ruffyscripter.commands.Command;
import de.jotomo.ruffyscripter.commands.CommandException;
import de.jotomo.ruffyscripter.commands.ConfirmAlertCommand;
import de.jotomo.ruffyscripter.commands.ReadBasalProfileCommand;
import de.jotomo.ruffyscripter.commands.ReadHistoryCommand;
import de.jotomo.ruffyscripter.commands.ReadPumpStateCommand;
import de.jotomo.ruffyscripter.commands.ReadReservoirLevelAndLastBolus;
import de.jotomo.ruffyscripter.commands.SetBasalProfileCommand;
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.
// 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 IRuffyService ruffyService;
// TODO never written
private String unrecoverableError = null;
@Nullable
@ -75,6 +77,8 @@ public class RuffyScripter implements RuffyCommands {
@Override
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);
}
@ -87,13 +91,11 @@ public class RuffyScripter implements RuffyCommands {
public void rtStopped() throws RemoteException {
log.debug("rtStopped callback invoked");
currentMenu = null;
connected = false;
}
@Override
public void rtStarted() throws RemoteException {
log.debug("rtStarted callback invoked");
connected = true;
}
@Override
@ -115,16 +117,6 @@ public class RuffyScripter implements RuffyCommands {
synchronized (screenlock) {
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
@ -174,9 +166,6 @@ public class RuffyScripter implements RuffyCommands {
return started;
}
private volatile boolean connected = false;
private volatile long lastDisconnected = 0;
private Thread idleDisconnectMonitorThread = new Thread(new Runnable() {
@Override
public void run() {
@ -184,25 +173,16 @@ public class RuffyScripter implements RuffyCommands {
try {
long now = System.currentTimeMillis();
long connectionTimeOutMs = 5000;
if (connected && activeCmd == null
&& now > lastCmdExecutionTime + connectionTimeOutMs
// don't disconnect too frequently, confuses ruffy?
&& now > lastDisconnected + 15 * 1000) {
if (ruffyService.isConnected() && activeCmd == null
&& now > lastCmdExecutionTime + connectionTimeOutMs) {
log.debug("Disconnecting after " + (connectionTimeOutMs / 1000) + "s inactivity timeout");
lastDisconnected = now;
ruffyService.doRTDisconnect();
connected = false;
// don't attempt anything fancy in the next 10s, let the pump settle
SystemClock.sleep(10 * 1000);
}
} catch (Exception e) {
// TODO do we need to catch this exception somewhere else too? right now it's
// converted into a command failure, but it's not classified as unrecoverable;
// 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);
log.debug("Exception in idle disconnect monitor thread, taking a break and then carrying on", e);
SystemClock.sleep(10 * 1000);
}
SystemClock.sleep(1000);
}
@ -219,18 +199,15 @@ public class RuffyScripter implements RuffyCommands {
return runCommand(new ReadPumpStateCommand());
}
@Override
public CommandResult readReservoirLevelAndLastBolus() {
return runCommand(new ReadReservoirLevelAndLastBolus());
}
public void returnToRootMenu() {
// returning to main menu using the 'back' key does not cause a vibration
MenuType menuType = getCurrentMenu().getType();
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);
pressBackKey();
waitForMenuUpdate();
@ -238,17 +215,11 @@ public class RuffyScripter implements RuffyCommands {
}
}
private static class Returnable {
CommandResult cmdResult;
}
/**
* Always returns a CommandResult, never throws
*/
public CommandResult runCommand(final Command cmd) {
/** Always returns a CommandResult, never throws */
private CommandResult runCommand(final Command cmd) {
log.debug("Attempting to run cmd: " + cmd);
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();
@ -262,59 +233,36 @@ public class RuffyScripter implements RuffyCommands {
activeCmd = cmd;
long connectStart = System.currentTimeMillis();
ensureConnected();
final Returnable returnable = new Returnable();
class CommandRunner {
public void run() {
log.debug("Connection ready to execute cmd " + cmd);
Thread cmdThread = new Thread(() -> {
try {
// Except for GetPumpStateCommand: fail on all requests if the pump is suspended.
// All trickery of not executing but returning success, so that AAPS can non-sensically TBR away when suspended
// are dangerous in the current model where commands are dispatched without checking state beforehand, so
// the above tactic would result in boluses not being applied and no warning being raised.
// (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) {
// TODO fail if currentMenu is not available?
Menu localCurrentMenu = currentMenu;
if (localCurrentMenu == null || localCurrentMenu.getType() == MenuType.STOP) {
if (cmd.needsRunMode()) {
returnable.cmdResult = new CommandResult().success(false).enacted(false).message("Pump is suspended");
activeCmd.getResult().success(false).message("Pump is suspended but operations requires to the pump to be running");
return;
}
}
log.debug("Connection ready to execute cmd " + cmd);
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);
long cmdStartTime = System.currentTimeMillis();
cmd.setScripter(RuffyScripter.this);
returnable.cmdResult = cmd.execute();
long cmdStartTime = System.currentTimeMillis();
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();
activeCmd.getResult().message(e.getMessage());
} catch (Exception e) {
log.error("Unexpected exception running cmd", e);
returnable.cmdResult = new CommandResult().exception(e).message("Unexpected exception running cmd");
activeCmd.getResult().message("Unexpected exception running cmd");
} finally {
lastCmdExecutionTime = System.currentTimeMillis();
}
}
}
Thread cmdThread = new Thread(new Runnable() {
@Override
public void run() {
new CommandRunner().run();
}
}, cmd.toString());
}, cmd.getClass().getSimpleName());
long executionStart = System.currentTimeMillis();
cmdThread.start();
@ -331,23 +279,6 @@ public class RuffyScripter implements RuffyCommands {
maxReconnectAttempts--;
cmdThread.interrupt();
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();
SystemClock.sleep(5000);
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) {
String msg = "Command " + cmd + " timed out after 4 min, check pump!";
log.error(msg);
return new CommandResult().success(false).enacted(false).message(msg);
activeCmd.getResult().success(false).message(msg);
break;
}
}
if (returnable.cmdResult.state == null) {
returnable.cmdResult.state = readPumpStateInternal();
}
activeCmd.getResult().state = readPumpStateInternal();
CommandResult result = activeCmd.getResult();
if (log.isDebugEnabled()) {
long connectDurationSec = (executionStart - connectStart) / 1000;
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) {
CommandResult commandResult = e.toCommandResult();
if (commandResult.state == null) commandResult.state = readPumpStateInternal();
return commandResult;
} catch (Exception e) {
// TODO catching E here AND in CommandRunner?
// TODO detect and report pump warnings/errors differently?
log.error("Error in ruffyscripter/ruffy", e);
try {
return new CommandResult()
.exception(e)
.message("Unexpected exception communication with ruffy: " + e.getMessage())
.state(readPumpStateInternal());
} catch (Exception e1) {
// nothing more we can try
log.debug("Command result: " + result);
log.debug("Connect: " + connectDurationSec + "s, execution: " + executionDurationSec + "s");
}
return new CommandResult().exception(e).message("Unexpected exception communication with ruffy: " + e.getMessage());
return result;
} catch (CommandException e) {
return activeCmd.getResult().success(false).message(e.getMessage()).state(readPumpStateInternal());
} catch (Exception e) {
log.error("Unexpected exception communication with ruffy", e);
return activeCmd.getResult().success(false).exception(e)
.message("Unexpected exception communication with ruffy: " + e.getMessage()).state(readPumpStateInternal());
} finally {
activeCmd = null;
}
@ -420,6 +342,17 @@ public class RuffyScripter implements RuffyCommands {
*
* @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() {
try {
log.debug("Connection was lost, trying to reconnect");
@ -475,58 +408,37 @@ public class RuffyScripter implements RuffyCommands {
*/
private void ensureConnected() {
try {
boolean menuUpdateRecentlyReceived = currentMenu != null && menuLastUpdated + 1000 > System.currentTimeMillis();
log.debug("ensureConnect, connected: " + connected + ", receiving menu updates: " + menuUpdateRecentlyReceived);
if (menuUpdateRecentlyReceived) {
log.debug("Pump is sending us menu updates, so we're connected");
if (ruffyService.isConnected()) {
log.debug("Already connected");
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;
log.debug("Connect init successful: " + connectInitSuccessful);
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 initialUpdateTime = menuLastUpdated;
long again = System.currentTimeMillis() + 30 * 1000;
while (initialUpdateTime == menuLastUpdated) {
if (System.currentTimeMillis() > timeoutExpired) {
throw new CommandException().message("Timeout connecting to pump");
throw new CommandException("Timeout connecting to pump");
}
SystemClock.sleep(50);
if (again < System.currentTimeMillis()) {
// TODO test
log.debug("Connecting taking long, forcing disconnect first");
ruffyService.doRTDisconnect();
SystemClock.sleep(2000);
log.debug("Connecting again");
ruffyService.doRTConnect();
SystemClock.sleep(1000);
again = System.currentTimeMillis() + 30 * 1000;
}
}
} catch (CommandException e) {
throw 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() {
PumpState state = new PumpState();
state.timestamp = System.currentTimeMillis();
Menu menu = currentMenu;
if (menu == null) {
return state;
@ -571,6 +484,16 @@ public class RuffyScripter implements RuffyCommands {
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
// TODO move into a new Operations(scripter) class commands can delegate to,
// 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?
while (currentMenu == null) {
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");
waitForMenuUpdate();
@ -678,21 +601,20 @@ public class RuffyScripter implements RuffyCommands {
ruffyService.rtSendKey(Key.NO_KEY, true);
log.debug("Releasing key");
} 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,
// but approach things differently;
public boolean waitForScreenUpdate(long timeout) {
private void waitForScreenUpdate() {
synchronized (screenlock) {
try {
screenlock.wait(timeout);
screenlock.wait((long) 2000); // usually ~500, occassionally up to 1100ms
} 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
@ -702,54 +624,19 @@ public class RuffyScripter implements RuffyCommands {
// 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
*/
public void waitForMenuUpdate() {
waitForMenuUpdate(60, "Timeout waiting for menu update");
}
private void waitForMenuUpdate(long timeoutInSeconds, String errorMessage) {
long timeoutExpired = System.currentTimeMillis() + timeoutInSeconds * 1000;
long initialUpdateTime = menuLastUpdated;
while (initialUpdateTime == menuLastUpdated) {
if (System.currentTimeMillis() > timeoutExpired) {
throw new CommandException().message(errorMessage);
}
SystemClock.sleep(50);
}
waitForScreenUpdate();
// long timeoutExpired = System.currentTimeMillis() + 60 * 1000;
// long initialUpdateTime = menuLastUpdated;
// while (initialUpdateTime == menuLastUpdated) {
// if (System.currentTimeMillis() > timeoutExpired) {
// throw new CommandException("Timeout waiting for menu update");
// }
// SystemClock.sleep(10);
// }
}
private void pressKey(final byte key) {
@ -758,7 +645,7 @@ public class RuffyScripter implements RuffyCommands {
SystemClock.sleep(150);
ruffyService.rtSendKey(Key.NO_KEY, true);
} 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.");
// }
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.");
}
pressMenuKey();
@ -792,7 +679,7 @@ public class RuffyScripter implements RuffyCommands {
long timeout = System.currentTimeMillis() + 60 * 1000;
while (getCurrentMenu().getType() == menuType) {
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);
}
@ -812,7 +699,7 @@ public class RuffyScripter implements RuffyCommands {
if (failureMessage == null) {
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);
retries = retries - 1;
} 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);
while (!expectedType.isInstance(value)) {
value = getCurrentMenu().getAttribute(attribute);
waitForScreenUpdate(1000);
waitForScreenUpdate();
retries--;
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;
@ -867,8 +754,8 @@ public class RuffyScripter implements RuffyCommands {
}
@Override
public CommandResult takeOverAlarms() {
return runCommand(new TakeOverAlarmsCommand());
public CommandResult confirmAlert(int warningCode) {
return runCommand(new ConfirmAlertCommand(warningCode));
}
@Override
@ -886,10 +773,15 @@ public class RuffyScripter implements RuffyCommands {
return runCommand(new SetBasalProfileCommand(basalProfile));
}
@Override
public CommandResult getDateAndTime() {
return new CommandResult().success(false).enacted(false);
}
@Override
public CommandResult setDateAndTime(Date date) {
// TODO I'm a faker!
return new CommandResult().success(true).enacted(false);
// TODO
return new CommandResult().success(false).enacted(false);
}
@Override
@ -906,4 +798,36 @@ public class RuffyScripter implements RuffyCommands {
public void unpair() {
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;
import java.util.Collections;
import java.util.List;
import de.jotomo.ruffy.spi.CommandResult;
import de.jotomo.ruffyscripter.RuffyScripter;
public abstract class BaseCommand implements Command {
// RS will inject itself here
protected RuffyScripter scripter;
protected CommandResult result = new CommandResult();
@Override
public void setScripter(RuffyScripter 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
// 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
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.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.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 {
private static final Logger log = LoggerFactory.getLogger(BolusCommand.class);
@ -26,14 +29,10 @@ public class BolusCommand extends BaseCommand {
private final BolusProgressReporter bolusProgressReporter;
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) {
this.bolus = bolus;
this.bolusProgressReporter = bolusProgressReporter;
this.result = new CommandResult();
}
@Override
@ -53,28 +52,26 @@ public class BolusCommand extends BaseCommand {
}
@Override
public CommandResult execute() {
public void execute() {
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);
enterBolusMenu();
inputBolusAmount();
verifyDisplayedBolusAmount();
// last chance to abort before confirm the bolus
if (cancelRequested) {
bolusProgressReporter.report(STOPPING, 0, 0);
scripter.returnToRootMenu();
bolusProgressReporter.report(STOPPED, 0, 0);
return new CommandResult().success(true).enacted(false)
.message("Bolus cancelled as per user request with no insulin delivered");
result.success = true;
return;
}
// confirm bolus
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
scripter.pressCheckKey();
result.enacted = true;
// the pump displays the entered bolus and waits a few seconds to let user check and cancel
while (scripter.getCurrentMenu().getType() == MenuType.BOLUS_ENTER) {
@ -83,47 +80,47 @@ public class BolusCommand extends BaseCommand {
scripter.pressUpKey();
// wait up to 1s for a BOLUS_CANCELLED alert, if it doesn't happen we missed
// the window, simply continue and let the next cancel attempt try its luck
boolean alertWasCancelled = scripter.confirmAlert("BOLUS CANCELLED", 1000);
boolean alertWasCancelled = scripter.confirmAlert(PumpWarningCodes.BOLUS_CANCELLED, 1000);
if (alertWasCancelled) {
bolusProgressReporter.report(STOPPED, 0, 0);
return new CommandResult().success(true).enacted(false)
.message("Bolus cancelled as per user request with no insulin delivered");
}
result.success = true;
return;
}
SystemClock.sleep(10);
}
}
// the bolus progress is displayed on the main menu
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU,
"Pump did not return to MAIN_MEU from BOLUS_ENTER to deliver bolus. "
+ "Check pump manually, the bolus might not have been delivered.");
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;
// it must be cleared so we can see the remaining bolus again;
// wait for bolus delivery to complete; the remaining units to deliver are counted down
boolean cancelInProgress = false;
double lastBolusReported = 0;
Double bolusRemaining = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS_REMAINING);
while (bolusRemaining != null) {
if (cancelRequested) {
// cancel running bolus by pressing up for 3s, while raise a BOLUS CANCELLED
// alert, unless the bolus finished within those 3s.
if (cancelRequested && !cancelInProgress) {
bolusProgressReporter.report(STOPPING, 0, 0);
scripter.pressKeyMs(RuffyScripter.Key.UP, 3000);
cancelInProgress = true;
new Thread(() -> scripter.pressKeyMs(RuffyScripter.Key.UP, 3000), "bolus-canceller").start();
}
if (scripter.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
// confirm warning alerts and update the result to indicate alerts occurred
int warningCode = scripter.readWarningCode();
if (warningCode == PumpWarningCodes.BOLUS_CANCELLED) {
scripter.confirmAlert(PumpWarningCodes.BOLUS_CANCELLED, 2000);
bolusProgressReporter.report(STOPPED, 0, 0);
// if the bolus finished while we attempted to cancel it, there'll be no alarm
long timeout = System.currentTimeMillis() + 2000;
while (scripter.getCurrentMenu().getType() != MenuType.WARNING_OR_ERROR && System.currentTimeMillis() < timeout) {
SystemClock.sleep(10);
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) {
log.debug("Delivering bolus, remaining: " + bolusRemaining);
@ -132,94 +129,13 @@ public class BolusCommand extends BaseCommand {
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);
bolusRemaining = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS_REMAINING);
}
bolusProgressReporter.report(DELIVERED, 100, bolus);
/*
// 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();
result.success = true;
} 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);
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
@ -265,7 +181,7 @@ public class BolusCommand extends BaseCommand {
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
double refreshedDisplayedBolus = scripter.readBlinkingValue(Double.class, MenuAttribute.BOLUS);
if (Math.abs(displayedBolus - refreshedDisplayedBolus) > 0.05) {
throw new CommandException().message("Failed to set bolus: bolus changed after input stopped from "
throw new CommandException("Failed to set bolus: bolus changed after input stopped from "
+ displayedBolus + " -> " + refreshedDisplayedBolus);
}
}

View file

@ -4,11 +4,7 @@ import org.monkey.d.ruffy.ruffy.driver.display.MenuType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.List;
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?
// 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 {
private static final Logger log = LoggerFactory.getLogger(CancelTbrCommand.class);
@Override
public List<String> validateArguments() {
return Collections.emptyList();
}
@Override
public String getReconnectAlarm() {
return "TBR CANCELLED";
}
@Override
public CommandResult execute() {
try {
public void execute() {
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU);
PumpState pumpState = scripter.readPumpStateInternal();
if (!pumpState.tbrActive) {
log.debug("No TBR active to cancel");
return new CommandResult()
.success(true)
// Technically, nothing was enacted, but AAPS needs this to recover
// 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();
}
setTbrCommand.execute();
result = setTbrCommand.result;
}
@Override

View file

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

View file

@ -1,52 +1,11 @@
package de.jotomo.ruffyscripter.commands;
import de.jotomo.ruffy.spi.CommandResult;
public class CommandException extends RuntimeException {
public boolean success = false;
public boolean enacted = false;
public Exception exception = null;
public String message = null;
public CommandException() {
public CommandException(String message) {
super(message);
}
public CommandException success(boolean success) {
this.success = success;
return this;
}
public CommandException enacted(boolean enacted) {
this.enacted = enacted;
return this;
}
public CommandException exception(Exception exception) {
this.exception = exception;
return this;
}
@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 + '\'' +
'}';
public CommandException(String message, Exception exception) {
super(message, exception);
}
}

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

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.MenuDate;
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.List;
import de.jotomo.ruffy.spi.CommandResult;
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.PumpHistoryRequest;
public class ReadHistoryCommand extends BaseCommand {
private static Logger log = LoggerFactory.getLogger(ReadHistoryCommand.class);
private final PumpHistoryRequest request;
private final PumpHistory history = new PumpHistory();
@ -26,24 +28,22 @@ public class ReadHistoryCommand extends BaseCommand {
}
@Override
public CommandResult execute() {
if (request.reservoirLevel) {
readReservoirLevel();
}
public void execute() {
if (request.bolusHistory != PumpHistoryRequest.SKIP
|| request.tbrHistory != PumpHistoryRequest.SKIP
|| request.pumpErrorHistory != PumpHistoryRequest.SKIP
|| request.tddHistory != PumpHistoryRequest.SKIP) {
|| request.pumpErrorHistory != PumpHistoryRequest.SKIP) {
scripter.navigateToMenu(MenuType.MY_DATA_MENU);
scripter.verifyMenuIsDisplayed(MenuType.MY_DATA_MENU);
scripter.pressCheckKey();
// TODO see how dana does time mangling for timezones
// bolus history
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_DATA);
if (request.bolusHistory != PumpHistoryRequest.SKIP) {
int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD);
if (totalRecords > 0) {
if (request.bolusHistory == PumpHistoryRequest.LAST) {
if (true || request.bolusHistory == PumpHistoryRequest.LAST) {
Bolus bolus = readBolusRecord();
history.bolusHistory.add(bolus);
} else {
@ -56,22 +56,21 @@ public class ReadHistoryCommand extends BaseCommand {
scripter.pressMenuKey();
scripter.verifyMenuIsDisplayed(MenuType.ERROR_DATA);
if (request.pumpErrorHistory != PumpHistoryRequest.SKIP) {
int code = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.WARNING);
String message = (String) scripter.getCurrentMenu().getAttribute(MenuAttribute.MESSAGE);
MenuDate date = (MenuDate) scripter.getCurrentMenu().getAttribute(MenuAttribute.DATE);
MenuTime time = (MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.TIME);
int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD);
if (totalRecords > 0) {
if (true || request.pumpErrorHistory == PumpHistoryRequest.LAST) {
PumpError error = readErrorRecord();
history.pumpErrorHistory.add(error);
} else {
readErrorRecords(request.pumpErrorHistory);
}
}
}
// tdd history
scripter.pressMenuKey();
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
scripter.pressMenuKey();
scripter.verifyMenuIsDisplayed(MenuType.TBR_DATA);
@ -82,64 +81,102 @@ public class ReadHistoryCommand extends BaseCommand {
// TODO start or end time?
MenuTime time = (MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.TIME);
}
*/
scripter.pressBackKey();
scripter.returnToRootMenu();
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) {
int record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_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();
if (requestedTime != PumpHistoryRequest.FULL && bolus.timestamp < requestedTime) {
if (requestedTime != PumpHistoryRequest.FULL && bolus.timestamp <= requestedTime) {
break;
}
history.bolusHistory.add(bolus);
scripter.pressDownKey();
scripter.waitForMenuUpdate();
record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD);
if (record == totalRecords) {
break;
}
}
}
@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().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);
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);
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 currentYear = new Date().getYear() + 1900;
if (currentMonth == 1 && date.getMonth() == 12) {
currentYear -= 1;
}
long recordDate = 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();
return new Date(currentYear - 1900, date.getMonth() - 1, date.getDay(), time.getHour(), time.getMinute()).getTime();
}
@Override
@ -149,5 +186,4 @@ public class ReadHistoryCommand extends BaseCommand {
", history=" + history +
'}';
}
}

View file

@ -1,25 +1,12 @@
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 {
@Override
public CommandResult execute() {
return new CommandResult().success(true).enacted(false).state(scripter.readPumpStateInternal());
public void execute() {
// 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
public String toString() {
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;
import java.util.ArrayList;
import java.util.List;
import de.jotomo.ruffy.spi.BasalProfile;
import de.jotomo.ruffyscripter.RuffyScripter;
import de.jotomo.ruffy.spi.CommandResult;
public class SetBasalProfileCommand extends BaseCommand {
public SetBasalProfileCommand(BasalProfile basalProfile) {
private final BasalProfile basalProfile;
public SetBasalProfileCommand(BasalProfile basalProfile) {
this.basalProfile = basalProfile;
}
@Override
public CommandResult execute() {
return null;
public void execute() {
// TODO
}
@Override
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.Locale;
import de.jotomo.ruffy.spi.CommandResult;
import de.jotomo.ruffy.spi.PumpWarningCodes;
public class SetTbrCommand extends BaseCommand {
private static final Logger log = LoggerFactory.getLogger(SetTbrCommand.class);
@ -58,8 +58,7 @@ public class SetTbrCommand extends BaseCommand {
}
@Override
public CommandResult execute() {
try {
public void execute() {
boolean cancellingTbr = percentage == 100;
enterTbrMenu();
@ -90,15 +89,11 @@ public class SetTbrCommand extends BaseCommand {
// check main menu shows the same values we just set
if (cancellingTbr) {
verifyMainMenuShowsNoActiveTbr();
return new CommandResult().success(true).enacted(true).message("TBR was cancelled");
result.success(true).enacted(true).message("TBR was cancelled");
} else {
verifyMainMenuShowsExpectedTbrActive();
return new CommandResult().success(true).enacted(true).message(
String.format(Locale.US, "TBR set to %d%% for %d min", percentage, duration));
}
} catch (CommandException e) {
return e.toCommandResult();
result.success(true).enacted(true)
.message(String.format(Locale.US, "TBR set to %d%% for %d min", percentage, duration));
}
}
@ -146,7 +141,7 @@ public class SetTbrCommand extends BaseCommand {
}
log.debug("Final displayed TBR percentage: " + displayedPercentage);
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);
}
@ -156,7 +151,7 @@ public class SetTbrCommand extends BaseCommand {
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET);
long refreshedDisplayedTbrPecentage = readDisplayedPercentage();
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 "
+ displayedPercentage + " -> " + refreshedDisplayedTbrPecentage);
}
@ -208,7 +203,7 @@ public class SetTbrCommand extends BaseCommand {
log.debug("Final displayed TBR duration: " + displayedDuration);
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);
}
@ -218,7 +213,7 @@ public class SetTbrCommand extends BaseCommand {
scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION);
long refreshedDisplayedTbrDuration = readDisplayedDuration();
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 "
+ 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,
// 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
scripter.confirmAlert("TBR CANCELLED", 5000);
scripter.confirmAlert(PumpWarningCodes.TBR_CANCELLED, 2000);
}
private void verifyMainMenuShowsNoActiveTbr() {
@ -242,7 +237,7 @@ public class SetTbrCommand extends BaseCommand {
Double tbrPercentage = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.TBR);
boolean runtimeDisplayed = scripter.getCurrentMenu().attributes().contains(MenuAttribute.RUNTIME);
if (tbrPercentage != 100 || runtimeDisplayed) {
throw new CommandException().message("Cancelling TBR failed, TBR is still set according to MAIN_MENU");
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 ...
if (!scripter.getCurrentMenu().attributes().contains(MenuAttribute.TBR) ||
!scripter.getCurrentMenu().attributes().contains(MenuAttribute.RUNTIME)) {
throw new CommandException().message("Setting TBR failed, according to MAIN_MENU no TBR is active");
throw new CommandException("Setting TBR failed, according to MAIN_MENU no TBR is active");
}
Double mmTbrPercentage = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.TBR);
MenuTime mmTbrDuration = (MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.RUNTIME);
@ -260,7 +255,7 @@ public class SetTbrCommand extends BaseCommand {
// 29 minutes and 59 seconds, so that 29 minutes are displayed
int mmTbrDurationInMinutes = mmTbrDuration.getHour() * 60 + mmTbrDuration.getMinute();
if (mmTbrPercentage != percentage || (mmTbrDurationInMinutes != duration && mmTbrDurationInMinutes + 1 != duration)) {
throw new CommandException().message("Setting TBR failed, TBR in MAIN_MENU differs from expected");
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();
}
}