AndroidAPS/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java

472 lines
17 KiB
Java
Raw Normal View History

2017-11-10 00:27:18 +01:00
package info.nightscout.androidaps.queue;
2018-01-14 21:42:36 +01:00
import android.content.Context;
import android.content.Intent;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
2017-11-10 00:27:18 +01:00
import android.text.Html;
import android.text.Spanned;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.LinkedList;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.DetailedBolusInfo;
import info.nightscout.androidaps.data.Profile;
import info.nightscout.androidaps.data.PumpEnactResult;
import info.nightscout.androidaps.events.EventBolusRequested;
2018-03-20 22:09:22 +01:00
import info.nightscout.androidaps.interfaces.Constraint;
import info.nightscout.androidaps.interfaces.PumpInterface;
import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin;
import info.nightscout.androidaps.plugins.Overview.Dialogs.BolusProgressDialog;
import info.nightscout.androidaps.plugins.Overview.Dialogs.BolusProgressHelperActivity;
import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification;
import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification;
import info.nightscout.androidaps.plugins.Overview.notifications.Notification;
2017-11-11 14:05:29 +01:00
import info.nightscout.androidaps.queue.commands.Command;
import info.nightscout.androidaps.queue.commands.CommandBolus;
import info.nightscout.androidaps.queue.commands.CommandCancelExtendedBolus;
import info.nightscout.androidaps.queue.commands.CommandCancelTempBasal;
import info.nightscout.androidaps.queue.commands.CommandExtendedBolus;
2017-11-22 22:09:58 +01:00
import info.nightscout.androidaps.queue.commands.CommandLoadEvents;
2017-11-11 14:05:29 +01:00
import info.nightscout.androidaps.queue.commands.CommandLoadHistory;
2018-03-14 00:57:48 +01:00
import info.nightscout.androidaps.queue.commands.CommandLoadTDDs;
2017-11-11 14:05:29 +01:00
import info.nightscout.androidaps.queue.commands.CommandReadStatus;
import info.nightscout.androidaps.queue.commands.CommandSMBBolus;
2017-11-11 14:05:29 +01:00
import info.nightscout.androidaps.queue.commands.CommandSetProfile;
import info.nightscout.androidaps.queue.commands.CommandTempBasalAbsolute;
import info.nightscout.androidaps.queue.commands.CommandTempBasalPercent;
2017-11-10 00:27:18 +01:00
/**
* Created by mike on 08.11.2017.
2018-01-14 21:42:36 +01:00
* <p>
2017-11-11 14:05:29 +01:00
* DATA FLOW:
* ---------
2018-01-14 21:42:36 +01:00
* <p>
2017-11-11 14:05:29 +01:00
* (request) - > ConfigBuilder.getCommandQueue().bolus(...)
2018-01-14 21:42:36 +01:00
* <p>
2017-11-11 14:05:29 +01:00
* app no longer waits for result but passes Callback
2018-01-14 21:42:36 +01:00
* <p>
2017-11-11 14:05:29 +01:00
* request is added to queue, if another request of the same type already exists in queue, it's removed prior adding
* but if request of the same type is currently executed (probably important only for bolus which is running long time), new request is declined
* new QueueThread is created and started if current if finished
* CommandReadStatus is added automatically before command if queue is empty
2018-01-14 21:42:36 +01:00
* <p>
2017-11-11 14:05:29 +01:00
* biggest change is we don't need exec pump commands in Handler because it's finished immediately
* command queueing if not realized by stacking in different Handlers and threads anymore but by internal queue with better control
2018-01-14 21:42:36 +01:00
* <p>
2017-11-11 14:05:29 +01:00
* QueueThread calls ConfigBuilder#connect which is passed to getActivePump().connect
* connect should be executed on background and return immediately. afterwards isConnecting() is expected to be true
2018-01-14 21:42:36 +01:00
* <p>
2017-11-11 14:05:29 +01:00
* while isConnecting() == true GUI is updated by posting connection progress
2018-01-14 21:42:36 +01:00
* <p>
2017-11-11 14:05:29 +01:00
* if connect is successful: isConnected() becomes true, isConnecting() becomes false
2018-01-14 21:42:36 +01:00
* CommandQueue starts calling execute() of commands. execute() is expected to be blocking (return after finish).
* callback with result is called after finish automatically
2017-11-11 14:05:29 +01:00
* if connect failed: isConnected() becomes false, isConnecting() becomes false
2018-01-14 21:42:36 +01:00
* connect() is called again
* <p>
2017-11-11 14:05:29 +01:00
* when queue is empty, disconnect is called
2017-11-10 00:27:18 +01:00
*/
public class CommandQueue {
private static Logger log = LoggerFactory.getLogger(CommandQueue.class);
private LinkedList<Command> queue = new LinkedList<>();
2018-01-14 21:42:36 +01:00
protected Command performing;
2017-11-10 00:27:18 +01:00
2017-11-11 14:05:29 +01:00
private QueueThread thread = null;
2017-11-10 00:27:18 +01:00
private PumpEnactResult executingNowError() {
2017-12-03 19:02:11 +01:00
return new PumpEnactResult().success(false).enacted(false).comment(MainApp.sResources.getString(R.string.executingrightnow));
2017-11-10 00:27:18 +01:00
}
2018-03-28 19:33:41 +02:00
public boolean isRunning(Command.CommandType type) {
2017-11-11 14:05:29 +01:00
if (performing != null && performing.commandType == type)
2017-11-10 00:27:18 +01:00
return true;
return false;
}
private synchronized void removeAll(Command.CommandType type) {
for (int i = 0; i < queue.size(); i++) {
if (queue.get(i).commandType == type) {
queue.remove(i);
}
}
}
2018-01-14 21:42:36 +01:00
private synchronized boolean isLastScheduled(Command.CommandType type) {
if (queue.size() > 0 && queue.get(queue.size() - 1).commandType == type) {
return true;
}
return false;
}
private synchronized void inject(Command command) {
// inject as a first command
log.debug("QUEUE: Adding as first: " + command.getClass().getSimpleName() + " - " + command.status());
2018-01-14 21:42:36 +01:00
queue.addFirst(command);
}
2017-11-10 00:27:18 +01:00
private synchronized void add(Command command) {
log.debug("QUEUE: Adding: " + command.getClass().getSimpleName() + " - " + command.status());
2017-11-10 00:27:18 +01:00
queue.add(command);
}
2017-11-11 14:05:29 +01:00
synchronized void pickup() {
2017-11-10 00:27:18 +01:00
performing = queue.poll();
}
2017-11-11 14:05:29 +01:00
synchronized void clear() {
performing = null;
for (int i = 0; i < queue.size(); i++) {
queue.get(i).cancel();
}
2017-11-10 00:27:18 +01:00
queue.clear();
}
public int size() {
return queue.size();
}
public Command performing() {
return performing;
}
public void resetPerforming() {
performing = null;
}
2017-11-11 14:05:29 +01:00
// After new command added to the queue
// start thread again if not already running
2018-01-14 21:42:36 +01:00
protected synchronized void notifyAboutNewCommand() {
while (thread != null && thread.getState() != Thread.State.TERMINATED && thread.waitingForDisconnect) {
log.debug("QUEUE: Waiting for previous thread finish");
SystemClock.sleep(500);
}
2017-11-11 14:05:29 +01:00
if (thread == null || thread.getState() == Thread.State.TERMINATED) {
thread = new QueueThread(this);
2017-11-10 00:27:18 +01:00
thread.start();
log.debug("QUEUE: Starting new thread");
} else {
log.debug("QUEUE: Thread is already running");
2017-11-11 14:05:29 +01:00
}
2017-11-10 00:27:18 +01:00
}
public static void independentConnect(String reason, Callback callback) {
CommandQueue tempCommandQueue = new CommandQueue();
tempCommandQueue.readStatus(reason, callback);
}
2017-11-10 00:27:18 +01:00
// returns true if command is queued
public boolean bolus(DetailedBolusInfo detailedBolusInfo, Callback callback) {
Command.CommandType type = detailedBolusInfo.isSMB ? Command.CommandType.SMB_BOLUS : Command.CommandType.BOLUS;
2018-04-18 16:13:02 +02:00
if(type.equals(Command.CommandType.BOLUS) && detailedBolusInfo.carbs > 0 && detailedBolusInfo.insulin == 0){
type = Command.CommandType.CARBS_ONLY_TREATMENT;
//Carbs only can be added in parallel as they can be "in the future".
} else {
if (isRunning(type)) {
if (callback != null)
callback.result(executingNowError()).run();
return false;
}
2017-11-10 00:27:18 +01:00
2018-04-18 16:13:02 +02:00
// remove all unfinished boluses
removeAll(type);
}
2017-11-10 00:27:18 +01:00
// apply constraints
2018-03-21 23:01:30 +01:00
detailedBolusInfo.insulin = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(detailedBolusInfo.insulin)).value();
2018-03-22 10:31:07 +01:00
detailedBolusInfo.carbs = MainApp.getConstraintChecker().applyCarbsConstraints(new Constraint<>((int) detailedBolusInfo.carbs)).value();
2017-11-10 00:27:18 +01:00
// add new command to queue
if (detailedBolusInfo.isSMB) {
add(new CommandSMBBolus(detailedBolusInfo, callback));
} else {
2018-04-18 16:13:02 +02:00
add(new CommandBolus(detailedBolusInfo, callback, type));
2018-04-18 16:23:40 +02:00
if(type.equals(Command.CommandType.BOLUS)) {
// Bring up bolus progress dialog (start here, so the dialog is shown when the bolus is requested,
// not when the Bolus command is starting. The command closes the dialog upon completion).
showBolusProgressDialog(detailedBolusInfo.insulin, detailedBolusInfo.context);
// Notify Wear about upcoming bolus
MainApp.bus().post(new EventBolusRequested(detailedBolusInfo.insulin));
}
}
2017-11-10 00:27:18 +01:00
notifyAboutNewCommand();
return true;
}
// returns true if command is queued
2018-03-20 22:09:22 +01:00
public boolean tempBasalAbsolute(double absoluteRate, int durationInMinutes, boolean enforceNew, Profile profile, Callback callback) {
2017-11-11 14:05:29 +01:00
if (isRunning(Command.CommandType.TEMPBASAL)) {
if (callback != null)
callback.result(executingNowError()).run();
2017-11-10 00:27:18 +01:00
return false;
}
2017-11-11 14:05:29 +01:00
// remove all unfinished
2017-11-10 00:27:18 +01:00
removeAll(Command.CommandType.TEMPBASAL);
2018-03-20 22:09:22 +01:00
Double rateAfterConstraints = MainApp.getConstraintChecker().applyBasalConstraints(new Constraint<>(absoluteRate), profile).value();
2017-11-10 00:27:18 +01:00
// add new command to queue
2018-03-20 22:09:22 +01:00
add(new CommandTempBasalAbsolute(rateAfterConstraints, durationInMinutes, enforceNew, profile, callback));
2017-11-10 00:27:18 +01:00
notifyAboutNewCommand();
return true;
}
// returns true if command is queued
public boolean tempBasalPercent(Integer percent, int durationInMinutes, boolean enforceNew, Profile profile, Callback callback) {
2017-11-11 14:05:29 +01:00
if (isRunning(Command.CommandType.TEMPBASAL)) {
if (callback != null)
callback.result(executingNowError()).run();
2017-11-10 00:27:18 +01:00
return false;
}
2017-11-11 14:05:29 +01:00
// remove all unfinished
2017-11-10 00:27:18 +01:00
removeAll(Command.CommandType.TEMPBASAL);
Integer percentAfterConstraints = MainApp.getConstraintChecker().applyBasalPercentConstraints(new Constraint<>(percent), profile).value();
2017-11-10 00:27:18 +01:00
// add new command to queue
add(new CommandTempBasalPercent(percentAfterConstraints, durationInMinutes, enforceNew, profile, callback));
2017-11-10 00:27:18 +01:00
notifyAboutNewCommand();
return true;
}
// returns true if command is queued
public boolean extendedBolus(double insulin, int durationInMinutes, Callback callback) {
2017-11-11 14:05:29 +01:00
if (isRunning(Command.CommandType.EXTENDEDBOLUS)) {
if (callback != null)
callback.result(executingNowError()).run();
2017-11-10 00:27:18 +01:00
return false;
}
2018-03-21 23:01:30 +01:00
Double rateAfterConstraints = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(insulin)).value();
// remove all unfinished
2017-11-10 00:27:18 +01:00
removeAll(Command.CommandType.EXTENDEDBOLUS);
// add new command to queue
add(new CommandExtendedBolus(rateAfterConstraints, durationInMinutes, callback));
2017-11-10 00:27:18 +01:00
notifyAboutNewCommand();
return true;
}
// returns true if command is queued
public boolean cancelTempBasal(boolean enforceNew, Callback callback) {
2017-11-11 14:05:29 +01:00
if (isRunning(Command.CommandType.TEMPBASAL)) {
if (callback != null)
callback.result(executingNowError()).run();
2017-11-10 00:27:18 +01:00
return false;
}
2017-11-11 14:05:29 +01:00
// remove all unfinished
2017-11-10 00:27:18 +01:00
removeAll(Command.CommandType.TEMPBASAL);
// add new command to queue
add(new CommandCancelTempBasal(enforceNew, callback));
notifyAboutNewCommand();
return true;
}
// returns true if command is queued
public boolean cancelExtended(Callback callback) {
2017-11-11 14:05:29 +01:00
if (isRunning(Command.CommandType.EXTENDEDBOLUS)) {
if (callback != null)
callback.result(executingNowError()).run();
2017-11-10 00:27:18 +01:00
return false;
}
2017-11-11 14:05:29 +01:00
// remove all unfinished
2017-11-10 00:27:18 +01:00
removeAll(Command.CommandType.EXTENDEDBOLUS);
// add new command to queue
add(new CommandCancelExtendedBolus(callback));
notifyAboutNewCommand();
return true;
}
// returns true if command is queued
public boolean setProfile(Profile profile, Callback callback) {
if (isThisProfileSet(profile)) {
log.debug("QUEUE: Correct profile already set");
if (callback != null)
callback.result(new PumpEnactResult().success(true).enacted(false)).run();
return false;
}
if (!MainApp.isEngineeringModeOrRelease()) {
Notification notification = new Notification(Notification.NOT_ENG_MODE_OR_RELEASE, MainApp.sResources.getString(R.string.not_eng_mode_or_release), Notification.URGENT);
MainApp.bus().post(new EventNewNotification(notification));
if (callback != null)
callback.result(new PumpEnactResult().success(false).comment(MainApp.sResources.getString(R.string.not_eng_mode_or_release))).run();
return false;
}
// Compare with pump limits
Profile.BasalValue[] basalValues = profile.getBasalValues();
PumpInterface pump = ConfigBuilderPlugin.getActivePump();
for (Profile.BasalValue basalValue : basalValues) {
if (basalValue.value < pump.getPumpDescription().basalMinimumRate) {
Notification notification = new Notification(Notification.BASAL_VALUE_BELOW_MINIMUM, MainApp.sResources.getString(R.string.basalvaluebelowminimum), Notification.URGENT);
MainApp.bus().post(new EventNewNotification(notification));
if (callback != null)
callback.result(new PumpEnactResult().success(false).comment(MainApp.sResources.getString(R.string.basalvaluebelowminimum))).run();
return false;
}
}
MainApp.bus().post(new EventDismissNotification(Notification.BASAL_VALUE_BELOW_MINIMUM));
// remove all unfinished
2017-11-10 00:27:18 +01:00
removeAll(Command.CommandType.BASALPROFILE);
// add new command to queue
add(new CommandSetProfile(profile, callback));
notifyAboutNewCommand();
return true;
}
2017-11-11 14:05:29 +01:00
// returns true if command is queued
public boolean readStatus(String reason, Callback callback) {
2018-01-14 21:42:36 +01:00
if (isLastScheduled(Command.CommandType.READSTATUS)) {
log.debug("QUEUE: READSTATUS " + reason + " ignored as duplicated");
if (callback != null)
callback.result(executingNowError()).run();
return false;
}
2017-11-11 14:05:29 +01:00
// remove all unfinished
//removeAll(Command.CommandType.READSTATUS);
2017-11-11 14:05:29 +01:00
// add new command to queue
add(new CommandReadStatus(reason, callback));
notifyAboutNewCommand();
return true;
}
// returns true if command is queued
public boolean loadHistory(byte type, Callback callback) {
if (isRunning(Command.CommandType.LOADHISTORY)) {
if (callback != null)
callback.result(executingNowError()).run();
return false;
}
// remove all unfinished
removeAll(Command.CommandType.LOADHISTORY);
// add new command to queue
add(new CommandLoadHistory(type, callback));
notifyAboutNewCommand();
return true;
}
2018-03-14 00:57:48 +01:00
// returns true if command is queued
public boolean loadTDDs(Callback callback) {
if (isRunning(Command.CommandType.LOADHISTORY)) {
if (callback != null)
callback.result(executingNowError()).run();
return false;
}
// remove all unfinished
removeAll(Command.CommandType.LOADHISTORY);
// add new command to queue
add(new CommandLoadTDDs(callback));
notifyAboutNewCommand();
return true;
}
2017-11-22 22:09:58 +01:00
// returns true if command is queued
public boolean loadEvents(Callback callback) {
if (isRunning(Command.CommandType.LOADEVENTS)) {
if (callback != null)
callback.result(executingNowError()).run();
return false;
}
// remove all unfinished
removeAll(Command.CommandType.LOADEVENTS);
// add new command to queue
add(new CommandLoadEvents(callback));
notifyAboutNewCommand();
return true;
}
2017-11-11 14:05:29 +01:00
public Spanned spannedStatus() {
2017-11-10 00:27:18 +01:00
String s = "";
2017-11-21 23:00:53 +01:00
int line = 0;
2017-11-10 00:27:18 +01:00
if (performing != null) {
2017-11-11 14:05:29 +01:00
s += "<b>" + performing.status() + "</b>";
2017-11-21 23:00:53 +01:00
line++;
2017-11-10 00:27:18 +01:00
}
for (int i = 0; i < queue.size(); i++) {
2017-11-21 23:00:53 +01:00
if (line != 0)
2017-11-11 14:05:29 +01:00
s += "<br>";
s += queue.get(i).status();
2017-11-21 23:00:53 +01:00
line++;
2017-11-10 00:27:18 +01:00
}
return Html.fromHtml(s);
}
public boolean isThisProfileSet(Profile profile) {
PumpInterface activePump = ConfigBuilderPlugin.getActivePump();
2018-03-17 08:12:07 +01:00
Profile current = MainApp.getConfigBuilder().getProfile();
if (activePump != null && current != null) {
boolean result = activePump.isThisProfileSet(profile);
if (!result) {
2018-03-17 08:12:07 +01:00
log.debug("Current profile: " + current.getData().toString());
log.debug("New profile: " + profile.getData().toString());
}
return result;
} else return true;
}
2018-01-14 21:42:36 +01:00
protected void showBolusProgressDialog(Double insulin, Context context) {
if (context != null) {
BolusProgressDialog bolusProgressDialog = new BolusProgressDialog();
bolusProgressDialog.setInsulin(insulin);
bolusProgressDialog.show(((AppCompatActivity) context).getSupportFragmentManager(), "BolusProgress");
} else {
Intent i = new Intent();
i.putExtra("insulin", insulin);
i.setClass(MainApp.instance(), BolusProgressHelperActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
MainApp.instance().startActivity(i);
}
}
2017-11-10 00:27:18 +01:00
}