From 8bae1a18e4a13b020491e9aac5e5dc058536c175 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Wed, 8 Nov 2017 22:22:51 +0100 Subject: [PATCH 01/14] CommandQueue --- .../androidaps/interfaces/PumpInterface.java | 2 +- .../plugins/PumpDanaRS/CommandQueue.java | 347 ++++++++++++++++++ app/src/main/res/values/strings.xml | 1 + 3 files changed, 349 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/CommandQueue.java diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.java b/app/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.java index 9da9cb0dd0..eb04ce85fb 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.java @@ -36,7 +36,7 @@ public interface PumpInterface { PumpEnactResult setExtendedBolus(Double insulin, Integer durationInMinutes); //some pumps might set a very short temp close to 100% as cancelling a temp can be noisy //when the cancel request is requested by the user (forced), the pump should always do a real cancel - PumpEnactResult cancelTempBasal(boolean force); + PumpEnactResult cancelTempBasal(boolean enforceNew); PumpEnactResult cancelExtendedBolus(); // Status to be passed to NS diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/CommandQueue.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/CommandQueue.java new file mode 100644 index 0000000000..d0651086ed --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/CommandQueue.java @@ -0,0 +1,347 @@ +package info.nightscout.androidaps.plugins.PumpDanaRS; + +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.utils.DecimalFormatter; + +/** + * Created by mike on 08.11.2017. + */ + +public class CommandQueue { + private static Logger log = LoggerFactory.getLogger(CommandQueue.class); + + enum CommandType { + BOLUS, + TEMPBASALPERCENT, + TEMPBASALABSOLUTE, + CANCELTEMPBASAL, + EXTENDEDBOLUS, + CANCELEXTENDEDBOLUS, + SETBASALPROFILE + } + + public class Command { + CommandType commandType; + Callback callback; + + int durationInMinutes; + + // Bolus + DetailedBolusInfo detailedBolusInfo; + // Temp basal percent + int percent; + // Temp basal absolute + double absoluteRate; + boolean enforceNew; + // Extended bolus + double insulin; + // Basal profile + Profile profile; + + public String status() { + switch (commandType) { + case BOLUS: + return "BOLUS " + DecimalFormatter.to1Decimal(detailedBolusInfo.insulin) + "U"; + case TEMPBASALPERCENT: + return "TEMPBASAL " + percent + "% " + durationInMinutes + " min"; + case TEMPBASALABSOLUTE: + return "TEMPBASAL " + absoluteRate + " U/h " + durationInMinutes + " min"; + case CANCELTEMPBASAL: + return "CANCEL TEMPBASAL"; + case EXTENDEDBOLUS: + return "EXTENDEDBOLUS " + insulin + " U " + durationInMinutes + " min"; + case CANCELEXTENDEDBOLUS: + return "CANCEL EXTENDEDBOLUS"; + case SETBASALPROFILE: + return "SETPROFILE"; + default: + return ""; + } + } + } + + public class Callback { + public PumpEnactResult result; + Runnable runnable; + + public Callback(Runnable runnable) { + this.runnable = runnable; + } + + public Callback result(PumpEnactResult result) { + this.result = result; + return this; + } + + public void run() { + runnable.run(); + } + } + + private LinkedList queue = new LinkedList<>(); + private Command performing; + + private PumpEnactResult executingNowError() { + PumpEnactResult result = new PumpEnactResult(); + result.success = false; + result.enacted = false; + result.comment = MainApp.sResources.getString(R.string.executingrightnow); + return result; + } + + public boolean isRunningTempBasal() { + if (performing != null) + if (performing.commandType == CommandType.TEMPBASALABSOLUTE || performing.commandType == CommandType.TEMPBASALPERCENT || performing.commandType == CommandType.CANCELTEMPBASAL) + return true; + return false; + } + + public boolean isRunningBolus() { + if (performing != null) + if (performing.commandType == CommandType.BOLUS) + return true; + return false; + } + + public boolean isRunningExtendedBolus() { + if (performing != null) + if (performing.commandType == CommandType.EXTENDEDBOLUS || performing.commandType == CommandType.CANCELEXTENDEDBOLUS) + return true; + return false; + } + + public boolean isRunningProfile() { + if (performing != null) + if (performing.commandType == CommandType.SETBASALPROFILE) + return true; + return false; + } + + private synchronized void removeAll(CommandType type) { + for (int i = 0; i < queue.size(); i++) { + Command c = queue.get(i); + switch (type) { + case TEMPBASALABSOLUTE: + case TEMPBASALPERCENT: + case CANCELTEMPBASAL: + if (c.commandType == CommandType.TEMPBASALABSOLUTE || c.commandType == CommandType.TEMPBASALPERCENT || c.commandType == CommandType.CANCELTEMPBASAL) { + queue.remove(i); + } + break; + case BOLUS: + if (c.commandType == CommandType.BOLUS) { + queue.remove(i); + } + break; + case EXTENDEDBOLUS: + case CANCELEXTENDEDBOLUS: + if (c.commandType == CommandType.EXTENDEDBOLUS || c.commandType == CommandType.CANCELEXTENDEDBOLUS) { + queue.remove(i); + } + break; + case SETBASALPROFILE: + if (c.commandType == CommandType.SETBASALPROFILE) { + queue.remove(i); + } + break; + } + } + } + + private synchronized void add(Command command) { + queue.add(command); + } + + private synchronized void pickup() { + performing = queue.poll(); + } + + public void clear() { + queue.clear(); + } + + private void notifyAboutNewCommand() { + + } + + // returns true if command is queued + public boolean bolus(DetailedBolusInfo detailedBolusInfo, Callback callback) { + if (isRunningBolus()) { + callback.result(executingNowError()).run(); + return false; + } + + // remove all unfinished boluese + removeAll(CommandType.BOLUS); + + // add new command to queue + Command command = new Command(); + command.commandType = CommandType.BOLUS; + command.detailedBolusInfo = detailedBolusInfo; + command.callback = callback; + add(command); + + notifyAboutNewCommand(); + + return true; + } + + // returns true if command is queued + public boolean tempBasalAbsolute(double absoluteRate, int durationInMinutes, boolean enforceNew, Callback callback) { + if (isRunningTempBasal()) { + callback.result(executingNowError()).run(); + return false; + } + + // remove all unfinished boluese + removeAll(CommandType.TEMPBASALABSOLUTE); + + // add new command to queue + Command command = new Command(); + command.commandType = CommandType.TEMPBASALABSOLUTE; + command.absoluteRate = absoluteRate; + command.durationInMinutes = durationInMinutes; + command.enforceNew = enforceNew; + command.callback = callback; + add(command); + + notifyAboutNewCommand(); + + return true; + } + + // returns true if command is queued + public boolean tempBasalPercent(int percent, int durationInMinutes, Callback callback) { + if (isRunningTempBasal()) { + callback.result(executingNowError()).run(); + return false; + } + + // remove all unfinished boluese + removeAll(CommandType.TEMPBASALPERCENT); + + // add new command to queue + Command command = new Command(); + command.commandType = CommandType.TEMPBASALPERCENT; + command.percent = percent; + command.durationInMinutes = durationInMinutes; + command.callback = callback; + add(command); + + notifyAboutNewCommand(); + + return true; + } + + // returns true if command is queued + public boolean extendedBolus(double insulin, int durationInMinutes, Callback callback) { + if (isRunningExtendedBolus()) { + callback.result(executingNowError()).run(); + return false; + } + + // remove all unfinished boluese + removeAll(CommandType.EXTENDEDBOLUS); + + // add new command to queue + Command command = new Command(); + command.commandType = CommandType.EXTENDEDBOLUS; + command.insulin = insulin; + command.durationInMinutes = durationInMinutes; + command.callback = callback; + add(command); + + notifyAboutNewCommand(); + + return true; + } + + // returns true if command is queued + public boolean cancelTempBasal(boolean enforceNew, Callback callback) { + if (isRunningTempBasal()) { + callback.result(executingNowError()).run(); + return false; + } + + // remove all unfinished boluese + removeAll(CommandType.CANCELTEMPBASAL); + + // add new command to queue + Command command = new Command(); + command.commandType = CommandType.CANCELTEMPBASAL; + command.enforceNew = enforceNew; + command.callback = callback; + add(command); + + notifyAboutNewCommand(); + + return true; + } + + // returns true if command is queued + public boolean cancelExtended(Callback callback) { + if (isRunningExtendedBolus()) { + callback.result(executingNowError()).run(); + return false; + } + + // remove all unfinished boluese + removeAll(CommandType.CANCELEXTENDEDBOLUS); + + // add new command to queue + Command command = new Command(); + command.commandType = CommandType.CANCELEXTENDEDBOLUS; + command.callback = callback; + add(command); + + notifyAboutNewCommand(); + + return true; + } + + // returns true if command is queued + public boolean setProfile(Profile profile, Callback callback) { + if (isRunningProfile()) { + callback.result(executingNowError()).run(); + return false; + } + + // remove all unfinished boluese + removeAll(CommandType.SETBASALPROFILE); + + // add new command to queue + Command command = new Command(); + command.commandType = CommandType.SETBASALPROFILE; + command.profile = profile; + command.callback = callback; + add(command); + + notifyAboutNewCommand(); + + return true; + } + + Spanned spannedStatus() { + String s = ""; + if (performing != null) { + s += "" + performing.status() + "
"; + } + for (int i = 0; i < queue.size(); i++) { + s += queue.get(i).status() + "
"; + } + return Html.fromHtml(s); + } + +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 32063b67b5..edfcc9ce02 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -750,5 +750,6 @@ Waiting for bolus end. Remaining %d sec. Processing event Starting bolus delivery + Command is executed right now From d40e0c97c4a78be9dbe228e36d88aaad4d12ec11 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Wed, 8 Nov 2017 23:40:54 +0100 Subject: [PATCH 02/14] simplify active pump access where possible --- .../DetermineBasalAdapterAMAJS.java | 8 ++-- .../plugins/OpenAPSAMA/OpenAPSAMAPlugin.java | 9 ++-- .../OpenAPSMA/DetermineBasalAdapterMAJS.java | 7 ++- .../plugins/OpenAPSMA/OpenAPSMAPlugin.java | 9 ++-- .../PersistentNotificationPlugin.java | 6 +-- .../CircadianPercentageProfileFragment.java | 10 ++--- .../CircadianPercentageProfilePlugin.java | 43 +++++++++---------- .../ProfileLocal/LocalProfileFragment.java | 15 +++---- .../plugins/ProfileNS/NSProfilePlugin.java | 4 +- .../ProfileSimple/SimpleProfileFragment.java | 5 +-- .../SmsCommunicatorPlugin.java | 34 +++++++-------- .../plugins/Treatments/TreatmentsPlugin.java | 16 +++---- .../plugins/Wear/ActionStringHandler.java | 6 +-- 13 files changed, 77 insertions(+), 95 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalAdapterAMAJS.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalAdapterAMAJS.java index 3102ac95d6..466026e986 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalAdapterAMAJS.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalAdapterAMAJS.java @@ -17,7 +17,6 @@ import org.slf4j.LoggerFactory; import java.io.IOException; import java.lang.reflect.InvocationTargetException; -import java.util.Date; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Constants; @@ -25,11 +24,10 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.data.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; +import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.TemporaryBasal; -import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.androidaps.plugins.Loop.ScriptReader; -import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.plugins.OpenAPSMA.LoggerCallback; import info.nightscout.utils.SP; @@ -186,7 +184,7 @@ public class DetermineBasalAdapterAMAJS { double minBg, double maxBg, double targetBg, - PumpInterface pump, + double basalrate, IobTotal[] iobArray, GlucoseStatus glucoseStatus, MealData mealData, @@ -210,7 +208,7 @@ public class DetermineBasalAdapterAMAJS { mProfile.put("max_daily_safety_multiplier", SP.getInt("openapsama_max_daily_safety_multiplier", 3)); mProfile.put("current_basal_safety_multiplier", SP.getInt("openapsama_current_basal_safety_multiplier", 4)); mProfile.put("skip_neutral_temps", true); - mProfile.put("current_basal", pump.getBaseBasalRate()); + mProfile.put("current_basal", basalrate); mProfile.put("temptargetSet", tempTargetSet); mProfile.put("autosens_adjust_targets", SP.getBoolean("openapsama_autosens_adjusttargets", true)); mProfile.put("min_5m_carbimpact", SP.getDouble("openapsama_min_5m_carbimpact", 3d)); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java index b64d4b6b08..ddf478b0af 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java @@ -14,15 +14,15 @@ import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; +import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.TempTarget; import info.nightscout.androidaps.interfaces.APSInterface; import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.IobCobCalculator.AutosensResult; import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.androidaps.plugins.Loop.APSResult; import info.nightscout.androidaps.plugins.Loop.ScriptReader; -import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateGui; import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateResultGui; import info.nightscout.utils.DateUtil; @@ -147,7 +147,6 @@ public class OpenAPSAMAPlugin implements PluginBase, APSInterface { GlucoseStatus glucoseStatus = GlucoseStatus.getGlucoseStatusData(); Profile profile = MainApp.getConfigBuilder().getProfile(); - PumpInterface pump = MainApp.getConfigBuilder(); if (profile == null) { MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.instance().getString(R.string.noprofileselected))); @@ -215,7 +214,7 @@ public class OpenAPSAMAPlugin implements PluginBase, APSInterface { if (!checkOnlyHardLimits(Profile.toMgdl(profile.getIsf(), units), "sens", 2, 900)) return; if (!checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.1, 10)) return; - if (!checkOnlyHardLimits(pump.getBaseBasalRate(), "current_basal", 0.01, 5)) return; + if (!checkOnlyHardLimits(ConfigBuilderPlugin.getActivePump().getBaseBasalRate(), "current_basal", 0.01, 5)) return; startPart = new Date(); if (MainApp.getConfigBuilder().isAMAModeEnabled()) { @@ -229,7 +228,7 @@ public class OpenAPSAMAPlugin implements PluginBase, APSInterface { start = new Date(); try { - determineBasalAdapterAMAJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg, pump, iobArray, glucoseStatus, mealData, + determineBasalAdapterAMAJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg, ConfigBuilderPlugin.getActivePump().getBaseBasalRate(), iobArray, glucoseStatus, mealData, lastAutosensResult.ratio, //autosensDataRatio isTempTarget, SafeParse.stringToDouble(SP.getString("openapsama_min_5m_carbimpact", "3.0"))//min_5m_carbimpact diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalAdapterMAJS.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalAdapterMAJS.java index a7e1d5da58..723b224f90 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalAdapterMAJS.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalAdapterMAJS.java @@ -22,9 +22,8 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.data.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; -import info.nightscout.androidaps.interfaces.PumpInterface; -import info.nightscout.androidaps.plugins.Loop.ScriptReader; import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.plugins.Loop.ScriptReader; import info.nightscout.utils.SP; public class DetermineBasalAdapterMAJS { @@ -155,7 +154,7 @@ public class DetermineBasalAdapterMAJS { double minBg, double maxBg, double targetBg, - PumpInterface pump, + double basalRate, IobTotal iobData, GlucoseStatus glucoseStatus, MealData mealData) throws JSONException { @@ -174,7 +173,7 @@ public class DetermineBasalAdapterMAJS { mProfile.put("carb_ratio", profile.getIc()); mProfile.put("sens", Profile.toMgdl(profile.getIsf().doubleValue(), units)); - mProfile.put("current_basal", pump.getBaseBasalRate()); + mProfile.put("current_basal", basalRate); if (units.equals(Constants.MMOL)) { mProfile.put("out_units", "mmol/L"); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAPlugin.java index a0d5cddd88..fda32166ce 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAPlugin.java @@ -14,13 +14,13 @@ import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; +import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.TempTarget; import info.nightscout.androidaps.interfaces.APSInterface; import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.Loop.APSResult; import info.nightscout.androidaps.plugins.Loop.ScriptReader; -import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateGui; import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateResultGui; import info.nightscout.utils.DateUtil; @@ -145,7 +145,6 @@ public class OpenAPSMAPlugin implements PluginBase, APSInterface { GlucoseStatus glucoseStatus = GlucoseStatus.getGlucoseStatusData(); Profile profile = MainApp.getConfigBuilder().getProfile(); - PumpInterface pump = MainApp.getConfigBuilder(); if (profile == null) { MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.instance().getString(R.string.noprofileselected))); @@ -213,11 +212,11 @@ public class OpenAPSMAPlugin implements PluginBase, APSInterface { if (!checkOnlyHardLimits(Profile.toMgdl(profile.getIsf(), units), "sens", 2, 900)) return; if (!checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.1, 10)) return; - if (!checkOnlyHardLimits(pump.getBaseBasalRate(), "current_basal", 0.01, 5)) return; + if (!checkOnlyHardLimits(ConfigBuilderPlugin.getActivePump().getBaseBasalRate(), "current_basal", 0.01, 5)) return; start = new Date(); try { - determineBasalAdapterMAJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg, pump, iobTotal, glucoseStatus, mealData); + determineBasalAdapterMAJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg, ConfigBuilderPlugin.getActivePump().getBaseBasalRate(), iobTotal, glucoseStatus, mealData); } catch (JSONException e) { log.error("Unhandled exception", e); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Persistentnotification/PersistentNotificationPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Persistentnotification/PersistentNotificationPlugin.java index f0745afe94..bf28c024a1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Persistentnotification/PersistentNotificationPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Persistentnotification/PersistentNotificationPlugin.java @@ -29,7 +29,7 @@ import info.nightscout.androidaps.events.EventRefreshOverview; import info.nightscout.androidaps.events.EventTempBasalChange; import info.nightscout.androidaps.events.EventTreatmentChange; import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.utils.DecimalFormatter; /** @@ -136,8 +136,6 @@ public class PersistentNotificationPlugin implements PluginBase { } } - PumpInterface pump = MainApp.getConfigBuilder(); - if (MainApp.getConfigBuilder().isTempBasalInProgress()) { TemporaryBasal activeTemp = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()); line1 += " " + activeTemp.toStringShort(); @@ -154,7 +152,7 @@ public class PersistentNotificationPlugin implements PluginBase { + ctx.getString(R.string.basal) + ": " + DecimalFormatter.to2Decimal(basalIob.basaliob) + "U)"; - String line3 = DecimalFormatter.to2Decimal(pump.getBaseBasalRate()) + " U/h"; + String line3 = DecimalFormatter.to2Decimal(ConfigBuilderPlugin.getActivePump().getBaseBasalRate()) + " U/h"; line3 += " - " + MainApp.getConfigBuilder().getProfileName(); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileCircadianPercentage/CircadianPercentageProfileFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileCircadianPercentage/CircadianPercentageProfileFragment.java index 53ceed17a8..c59f1c9f5e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileCircadianPercentage/CircadianPercentageProfileFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileCircadianPercentage/CircadianPercentageProfileFragment.java @@ -35,11 +35,11 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.events.EventInitializationChanged; import info.nightscout.androidaps.events.EventProfileSwitchChange; -import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.Careportal.CareportalFragment; import info.nightscout.androidaps.plugins.Careportal.Dialogs.NewNSTreatmentDialog; import info.nightscout.androidaps.plugins.Careportal.OptionsToShow; import info.nightscout.androidaps.plugins.Common.SubscriberFragment; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.utils.DecimalFormatter; import info.nightscout.utils.SafeParse; @@ -151,8 +151,7 @@ public class CircadianPercentageProfileFragment extends SubscriberFragment { iceditIcon = (ImageView) layout.findViewById(R.id.circadianpercentageprofile_icedit); isfeditIcon = (ImageView) layout.findViewById(R.id.circadianpercentageprofile_isfedit); - PumpInterface pump = MainApp.getConfigBuilder(); - if (!pump.getPumpDescription().isTempBasalCapable) { + if (!ConfigBuilderPlugin.getActivePump().getPumpDescription().isTempBasalCapable) { layout.findViewById(R.id.circadianpercentageprofile_baseprofilebasal_layout).setVisibility(View.GONE); } @@ -334,7 +333,8 @@ public class CircadianPercentageProfileFragment extends SubscriberFragment { adb.setPositiveButton("MIGRATE", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { CircadianPercentageProfilePlugin.migrateToLP(); - } }); + } + }); adb.setNegativeButton("Cancel", null); adb.show(); } @@ -366,7 +366,7 @@ public class CircadianPercentageProfileFragment extends SubscriberFragment { } private void customSnackbar(View view, final String Msg, Object snackbarCaller) { - if(mSnackBar!= null) mSnackBar.dismiss(); + if (mSnackBar != null) mSnackBar.dismiss(); this.snackbarCaller = snackbarCaller; if (timeshiftViewHint || percentageViewHint) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileCircadianPercentage/CircadianPercentageProfilePlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileCircadianPercentage/CircadianPercentageProfilePlugin.java index ea4f32e794..949e7d1cf3 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileCircadianPercentage/CircadianPercentageProfilePlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileCircadianPercentage/CircadianPercentageProfilePlugin.java @@ -16,12 +16,10 @@ import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.data.ProfileStore; import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.interfaces.ProfileInterface; -import info.nightscout.androidaps.data.ProfileStore; -import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.Careportal.Dialogs.NewNSTreatmentDialog; -import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.ProfileLocal.LocalProfilePlugin; import info.nightscout.utils.DecimalFormatter; import info.nightscout.utils.NSUpload; @@ -173,34 +171,33 @@ public class CircadianPercentageProfilePlugin implements PluginBase, ProfileInte String msg = ""; - if (!fragmentEnabled){ - msg+= "NO CPP!" + "\n"; + if (!fragmentEnabled) { + msg += "NO CPP!" + "\n"; } //check for validity if (percentage < Constants.CPP_MIN_PERCENTAGE || percentage > Constants.CPP_MAX_PERCENTAGE) { - msg+= String.format(MainApp.sResources.getString(R.string.openapsma_valueoutofrange), "Profile-Percentage") + "\n"; + msg += String.format(MainApp.sResources.getString(R.string.openapsma_valueoutofrange), "Profile-Percentage") + "\n"; } if (timeshift < 0 || timeshift > 23) { - msg+= String.format(MainApp.sResources.getString(R.string.openapsma_valueoutofrange), "Profile-Timeshift") + "\n"; + msg += String.format(MainApp.sResources.getString(R.string.openapsma_valueoutofrange), "Profile-Timeshift") + "\n"; } - if(!SP.getBoolean("syncprofiletopump", false)){ - msg+= MainApp.sResources.getString(R.string.syncprofiletopump_title) + " " + MainApp.sResources.getString(R.string.cpp_sync_setting_missing) + "\n"; + if (!SP.getBoolean("syncprofiletopump", false)) { + msg += MainApp.sResources.getString(R.string.syncprofiletopump_title) + " " + MainApp.sResources.getString(R.string.cpp_sync_setting_missing) + "\n"; } - final PumpInterface pump = MainApp.getConfigBuilder(); final Profile profile = MainApp.getConfigBuilder().getProfile(); - if (pump == null || profile == null || profile.getBasal() == null){ - msg+= MainApp.sResources.getString(R.string.cpp_notloadedplugins) + "\n"; + if (profile == null || profile.getBasal() == null) { + msg += MainApp.sResources.getString(R.string.cpp_notloadedplugins) + "\n"; } - if(!"".equals(msg)) { + if (!"".equals(msg)) { msg += MainApp.sResources.getString(R.string.cpp_valuesnotstored); return msg; } //store profile - this.timeshift= timeshift; - this.percentage = percentage; + this.timeshift = timeshift; + this.percentage = percentage; storeSettings(); @@ -218,7 +215,7 @@ public class CircadianPercentageProfilePlugin implements PluginBase, ProfileInte return msg; } - public static void migrateToLP(){ + public static void migrateToLP() { SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(MainApp.instance().getApplicationContext()); SharedPreferences.Editor editor = settings.edit(); editor.putBoolean("LocalProfile" + "mmol", SP.getBoolean(SETTINGS_PREFIX + "mmol", false)); @@ -232,7 +229,7 @@ public class CircadianPercentageProfilePlugin implements PluginBase, ProfileInte JSONArray targetHigh = new JSONArray().put(new JSONObject().put("time", "00:00").put("timeAsSeconds", 0).put("value", SP.getDouble(SETTINGS_PREFIX + "targethigh", 120d))); editor.putString("LocalProfile" + "targetlow", targetLow.toString()); editor.putString("LocalProfile" + "targethigh", targetHigh.toString()); - } catch (JSONException e) { + } catch (JSONException e) { e.printStackTrace(); } editor.commit(); @@ -247,19 +244,19 @@ public class CircadianPercentageProfilePlugin implements PluginBase, ProfileInte } - public static String getLPisf(){ + public static String getLPisf() { return getLPConversion("baseisf", 35d); } - public static String getLPic(){ + public static String getLPic() { return getLPConversion("baseic", 4); } - public static String getLPbasal(){ + public static String getLPbasal() { return getLPConversion("basebasal", 1); } - public static String getLPConversion(String type, double defaultValue){ + public static String getLPConversion(String type, double defaultValue) { try { JSONArray jsonArray = new JSONArray(); double last = -1d; @@ -269,10 +266,10 @@ public class CircadianPercentageProfilePlugin implements PluginBase, ProfileInte String time; DecimalFormat df = new DecimalFormat("00"); time = df.format(i) + ":00"; - if(last != value) { + if (last != value) { jsonArray.put(new JSONObject().put("time", time).put("timeAsSeconds", i * 60 * 60).put("value", value)); } - last = value; + last = value; } return jsonArray.toString(); } catch (JSONException e) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileLocal/LocalProfileFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileLocal/LocalProfileFragment.java index 24965861df..63eafe8390 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileLocal/LocalProfileFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileLocal/LocalProfileFragment.java @@ -23,11 +23,11 @@ import java.text.DecimalFormat; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.events.EventInitializationChanged; -import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.Careportal.CareportalFragment; import info.nightscout.androidaps.plugins.Careportal.Dialogs.NewNSTreatmentDialog; import info.nightscout.androidaps.plugins.Careportal.OptionsToShow; import info.nightscout.androidaps.plugins.Common.SubscriberFragment; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.utils.DecimalFormatter; import info.nightscout.utils.NumberPicker; import info.nightscout.utils.SafeParse; @@ -59,8 +59,8 @@ public class LocalProfileFragment extends SubscriberFragment { @Override public void run() { localProfilePlugin.storeSettings(); - if(basalView!=null){ - basalView.updateLabel(MainApp.sResources.getString(R.string.nsprofileview_basal_label)+ ": "+ getSumLabel()); + if (basalView != null) { + basalView.updateLabel(MainApp.sResources.getString(R.string.nsprofileview_basal_label) + ": " + getSumLabel()); } } }; @@ -91,12 +91,11 @@ public class LocalProfileFragment extends SubscriberFragment { mmolView = (RadioButton) layout.findViewById(R.id.localprofile_mmol); icView = new TimeListEdit(getContext(), layout, R.id.localprofile_ic, MainApp.sResources.getString(R.string.nsprofileview_ic_label) + ":", getPlugin().ic, null, 0.1d, new DecimalFormat("0.0"), save); isfView = new TimeListEdit(getContext(), layout, R.id.localprofile_isf, MainApp.sResources.getString(R.string.nsprofileview_isf_label) + ":", getPlugin().isf, null, 0.1d, new DecimalFormat("0.0"), save); - basalView = new TimeListEdit(getContext(), layout, R.id.localprofile_basal, MainApp.sResources.getString(R.string.nsprofileview_basal_label)+ ": " + getSumLabel(), getPlugin().basal, null, 0.01d, new DecimalFormat("0.00"), save); - targetView = new TimeListEdit(getContext(), layout, R.id.localprofile_target, MainApp.sResources.getString(R.string.nsprofileview_target_label)+ ":", getPlugin().targetLow, getPlugin().targetHigh, 0.1d, new DecimalFormat("0.0"), save); + basalView = new TimeListEdit(getContext(), layout, R.id.localprofile_basal, MainApp.sResources.getString(R.string.nsprofileview_basal_label) + ": " + getSumLabel(), getPlugin().basal, null, 0.01d, new DecimalFormat("0.00"), save); + targetView = new TimeListEdit(getContext(), layout, R.id.localprofile_target, MainApp.sResources.getString(R.string.nsprofileview_target_label) + ":", getPlugin().targetLow, getPlugin().targetHigh, 0.1d, new DecimalFormat("0.0"), save); profileswitchButton = (Button) layout.findViewById(R.id.localprofile_profileswitch); - PumpInterface pump = MainApp.getConfigBuilder(); - if (!pump.getPumpDescription().isTempBasalCapable) { + if (!ConfigBuilderPlugin.getActivePump().getPumpDescription().isTempBasalCapable) { layout.findViewById(R.id.localprofile_basal).setVisibility(View.GONE); } @@ -148,7 +147,7 @@ public class LocalProfileFragment extends SubscriberFragment { @NonNull public String getSumLabel() { - return " ∑" + DecimalFormatter.to2Decimal(localProfilePlugin.getProfile().getDefaultProfile().baseBasalSum()) +"U"; + return " ∑" + DecimalFormatter.to2Decimal(localProfilePlugin.getProfile().getDefaultProfile().baseBasalSum()) + "U"; } @Subscribe diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileNS/NSProfilePlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileNS/NSProfilePlugin.java index e4fdabecf9..3476d3682d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileNS/NSProfilePlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileNS/NSProfilePlugin.java @@ -19,6 +19,7 @@ import info.nightscout.androidaps.data.ProfileStore; import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.interfaces.ProfileInterface; import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.ProfileNS.events.EventNSProfileUpdateGUI; import info.nightscout.androidaps.plugins.SmsCommunicator.SmsCommunicatorPlugin; import info.nightscout.utils.SP; @@ -119,9 +120,8 @@ public class NSProfilePlugin implements PluginBase, ProfileInterface { profile = new ProfileStore(newProfile.getData()); storeNSProfile(); MainApp.bus().post(new EventNSProfileUpdateGUI()); - PumpInterface pump = MainApp.getConfigBuilder(); if (SP.getBoolean("syncprofiletopump", false)) { - if (pump.setNewBasalProfile(MainApp.getConfigBuilder().getProfile()) == PumpInterface.SUCCESS) { + if (ConfigBuilderPlugin.getActivePump().setNewBasalProfile(MainApp.getConfigBuilder().getProfile()) == PumpInterface.SUCCESS) { SmsCommunicatorPlugin smsCommunicatorPlugin = MainApp.getSpecificPlugin(SmsCommunicatorPlugin.class); if (smsCommunicatorPlugin != null && smsCommunicatorPlugin.isEnabled(PluginBase.GENERAL)) { smsCommunicatorPlugin.sendNotificationToAllNumbers(MainApp.sResources.getString(R.string.profile_set_ok)); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileSimple/SimpleProfileFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileSimple/SimpleProfileFragment.java index 88057a68c1..7868c3a5bb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileSimple/SimpleProfileFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileSimple/SimpleProfileFragment.java @@ -21,11 +21,11 @@ import org.slf4j.LoggerFactory; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.events.EventInitializationChanged; -import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.Careportal.CareportalFragment; import info.nightscout.androidaps.plugins.Careportal.Dialogs.NewNSTreatmentDialog; import info.nightscout.androidaps.plugins.Careportal.OptionsToShow; import info.nightscout.androidaps.plugins.Common.SubscriberFragment; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.utils.SafeParse; public class SimpleProfileFragment extends SubscriberFragment { @@ -56,8 +56,7 @@ public class SimpleProfileFragment extends SubscriberFragment { targethighView = (EditText) layout.findViewById(R.id.simpleprofile_targethigh); profileswitchButton = (Button) layout.findViewById(R.id.simpleprofile_profileswitch); - PumpInterface pump = MainApp.getConfigBuilder(); - if (!pump.getPumpDescription().isTempBasalCapable) { + if (!ConfigBuilderPlugin.getActivePump().getPumpDescription().isTempBasalCapable) { layout.findViewById(R.id.simpleprofile_basalrate).setVisibility(View.GONE); layout.findViewById(R.id.simpleprofile_basalrate_label).setVisibility(View.GONE); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/SmsCommunicator/SmsCommunicatorPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/SmsCommunicator/SmsCommunicatorPlugin.java index 9fdb0152d0..bb30a1a4b8 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/SmsCommunicator/SmsCommunicatorPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/SmsCommunicator/SmsCommunicatorPlugin.java @@ -477,24 +477,22 @@ public class SmsCommunicatorPlugin implements PluginBase { bolusWaitingForConfirmation.confirmCode.equals(splited[0]) && System.currentTimeMillis() - bolusWaitingForConfirmation.date.getTime() < CONFIRM_TIMEOUT) { bolusWaitingForConfirmation.processed = true; PumpInterface pumpInterface = MainApp.getConfigBuilder(); - if (pumpInterface != null) { - danaRPlugin = MainApp.getSpecificPlugin(DanaRPlugin.class); - DetailedBolusInfo detailedBolusInfo = new DetailedBolusInfo(); - detailedBolusInfo.insulin = bolusWaitingForConfirmation.bolusRequested; - detailedBolusInfo.source = Source.USER; - PumpEnactResult result = pumpInterface.deliverTreatment(detailedBolusInfo); - if (result.success) { - reply = String.format(MainApp.sResources.getString(R.string.smscommunicator_bolusdelivered), result.bolusDelivered); - if (danaRPlugin != null) - reply += "\n" + danaRPlugin.shortStatus(true); - lastRemoteBolusTime = new Date(); - sendSMSToAllNumbers(new Sms(receivedSms.phoneNumber, reply, new Date())); - } else { - reply = MainApp.sResources.getString(R.string.smscommunicator_bolusfailed); - if (danaRPlugin != null) - reply += "\n" + danaRPlugin.shortStatus(true); - sendSMS(new Sms(receivedSms.phoneNumber, reply, new Date())); - } + danaRPlugin = MainApp.getSpecificPlugin(DanaRPlugin.class); + DetailedBolusInfo detailedBolusInfo = new DetailedBolusInfo(); + detailedBolusInfo.insulin = bolusWaitingForConfirmation.bolusRequested; + detailedBolusInfo.source = Source.USER; + PumpEnactResult result = pumpInterface.deliverTreatment(detailedBolusInfo); + if (result.success) { + reply = String.format(MainApp.sResources.getString(R.string.smscommunicator_bolusdelivered), result.bolusDelivered); + if (danaRPlugin != null) + reply += "\n" + danaRPlugin.shortStatus(true); + lastRemoteBolusTime = new Date(); + sendSMSToAllNumbers(new Sms(receivedSms.phoneNumber, reply, new Date())); + } else { + reply = MainApp.sResources.getString(R.string.smscommunicator_bolusfailed); + if (danaRPlugin != null) + reply += "\n" + danaRPlugin.shortStatus(true); + sendSMS(new Sms(receivedSms.phoneNumber, reply, new Date())); } } else if (tempBasalWaitingForConfirmation != null && !tempBasalWaitingForConfirmation.processed && tempBasalWaitingForConfirmation.confirmCode.equals(splited[0]) && System.currentTimeMillis() - tempBasalWaitingForConfirmation.date.getTime() < CONFIRM_TIMEOUT) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java index d7c9d1eafe..b1183a6429 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java @@ -15,10 +15,10 @@ import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.data.Intervals; import info.nightscout.androidaps.data.Iob; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; -import info.nightscout.androidaps.data.Intervals; import info.nightscout.androidaps.data.NonOverlappingIntervals; import info.nightscout.androidaps.data.OverlappingIntervals; import info.nightscout.androidaps.data.Profile; @@ -33,8 +33,8 @@ import info.nightscout.androidaps.events.EventReloadTempBasalData; import info.nightscout.androidaps.events.EventReloadTreatmentData; import info.nightscout.androidaps.events.EventTempTargetChange; import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.interfaces.TreatmentsInterface; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.IobCobCalculator.AutosensData; import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.utils.SP; @@ -200,8 +200,8 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { if (!t.isSMB) { // instead of dividing the DIA that only worked on the bilinear curves, // multiply the time the treatment is seen active. - long timeSinceTreatment = time - t.date; - long snoozeTime = t.date + (long)(timeSinceTreatment * SP.getDouble("openapsama_bolussnooze_dia_divisor", 2.0)); + long timeSinceTreatment = time - t.date; + long snoozeTime = t.date + (long) (timeSinceTreatment * SP.getDouble("openapsama_bolussnooze_dia_divisor", 2.0)); Iob bIOB = t.iobCalc(snoozeTime, dia); total.bolussnooze += bIOB.iobContrib; } else { @@ -384,18 +384,16 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { @Override public double getTempBasalAbsoluteRateHistory() { - PumpInterface pump = MainApp.getConfigBuilder(); - TemporaryBasal tb = getTempBasalFromHistory(System.currentTimeMillis()); if (tb != null) { - if (tb.isFakeExtended){ - double baseRate = pump.getBaseBasalRate(); + if (tb.isFakeExtended) { + double baseRate = ConfigBuilderPlugin.getActivePump().getBaseBasalRate(); double tempRate = baseRate + tb.netExtendedRate; return tempRate; } else if (tb.isAbsolute) { return tb.absoluteRate; } else { - double baseRate = pump.getBaseBasalRate(); + double baseRate = ConfigBuilderPlugin.getActivePump().getBaseBasalRate(); double tempRate = baseRate * (tb.percentRate / 100d); return tempRate; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Wear/ActionStringHandler.java b/app/src/main/java/info/nightscout/androidaps/plugins/Wear/ActionStringHandler.java index a7b90775f1..c7030149fa 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Wear/ActionStringHandler.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Wear/ActionStringHandler.java @@ -13,12 +13,12 @@ import java.util.Date; import java.util.LinkedList; import java.util.List; -import info.nightscout.androidaps.BuildConfig; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Constants; 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.db.BgReading; import info.nightscout.androidaps.db.DanaRHistoryRecord; @@ -35,7 +35,6 @@ import info.nightscout.androidaps.plugins.Actions.dialogs.FillDialog; import info.nightscout.androidaps.plugins.Careportal.Dialogs.NewNSTreatmentDialog; import info.nightscout.androidaps.plugins.Loop.APSResult; import info.nightscout.androidaps.plugins.Loop.LoopPlugin; -import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPlugin; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump; @@ -586,10 +585,9 @@ public class ActionStringHandler { if(!SP.getBoolean("syncprofiletopump", false)){ msg+= MainApp.sResources.getString(R.string.syncprofiletopump_title) + " " + MainApp.sResources.getString(R.string.cpp_sync_setting_missing) + "\n"; } - final PumpInterface pump = MainApp.getConfigBuilder(); final Profile profile = MainApp.getConfigBuilder().getProfile(); - if (pump == null || profile == null || profile.getBasal() == null){ + if (profile == null || profile.getBasal() == null){ msg+= MainApp.sResources.getString(R.string.cpp_notloadedplugins) + "\n"; } if(!"".equals(msg)) { From a186ce6468c1be8c1e279f48589da7a287ad0e3a Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Fri, 10 Nov 2017 00:27:18 +0100 Subject: [PATCH 03/14] more command queue code --- .../androidaps/interfaces/PumpInterface.java | 10 +- .../ConfigBuilder/ConfigBuilderPlugin.java | 48 ++- .../plugins/ProfileNS/NSProfilePlugin.java | 3 +- .../plugins/PumpDanaR/DanaRPlugin.java | 34 +- .../PumpDanaR/Dialogs/ProfileViewDialog.java | 6 +- .../plugins/PumpDanaR/comm/MsgCheckValue.java | 2 +- .../PumpDanaR/comm/MsgInitConnStatusTime.java | 2 +- .../PumpDanaRKorean/DanaRKoreanPlugin.java | 34 +- .../PumpDanaRKorean/comm/MsgCheckValue_k.java | 2 +- .../comm/MsgInitConnStatusTime_k.java | 2 +- .../plugins/PumpDanaRS/CommandQueue.java | 347 ------------------ .../plugins/PumpDanaRS/DanaRSPlugin.java | 35 +- .../plugins/PumpDanaRS/services/BLEComm.java | 30 -- .../plugins/PumpDanaRv2/DanaRv2Plugin.java | 34 +- .../PumpDanaRv2/comm/MsgCheckValue_v2.java | 4 +- .../androidaps/plugins/PumpMDI/MDIPlugin.java | 24 +- .../PumpVirtual/VirtualPumpPlugin.java | 26 +- .../nightscout/androidaps/queue/Callback.java | 24 ++ .../nightscout/androidaps/queue/Command.java | 21 ++ .../androidaps/queue/CommandBolus.java | 31 ++ .../queue/CommandCancelExtendedBolus.java | 28 ++ .../queue/CommandCancelTempBasal.java | 30 ++ .../queue/CommandExtendedBolus.java | 32 ++ .../androidaps/queue/CommandQueue.java | 235 ++++++++++++ .../androidaps/queue/CommandReadStatus.java | 25 ++ .../androidaps/queue/CommandSetProfile.java | 31 ++ .../queue/CommandTempBasalAbsolute.java | 34 ++ .../queue/CommandTempBasalPercent.java | 32 ++ .../androidaps/queue/QueueThread.java | 69 ++++ 29 files changed, 786 insertions(+), 449 deletions(-) delete mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/CommandQueue.java create mode 100644 app/src/main/java/info/nightscout/androidaps/queue/Callback.java create mode 100644 app/src/main/java/info/nightscout/androidaps/queue/Command.java create mode 100644 app/src/main/java/info/nightscout/androidaps/queue/CommandBolus.java create mode 100644 app/src/main/java/info/nightscout/androidaps/queue/CommandCancelExtendedBolus.java create mode 100644 app/src/main/java/info/nightscout/androidaps/queue/CommandCancelTempBasal.java create mode 100644 app/src/main/java/info/nightscout/androidaps/queue/CommandExtendedBolus.java create mode 100644 app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java create mode 100644 app/src/main/java/info/nightscout/androidaps/queue/CommandReadStatus.java create mode 100644 app/src/main/java/info/nightscout/androidaps/queue/CommandSetProfile.java create mode 100644 app/src/main/java/info/nightscout/androidaps/queue/CommandTempBasalAbsolute.java create mode 100644 app/src/main/java/info/nightscout/androidaps/queue/CommandTempBasalPercent.java create mode 100644 app/src/main/java/info/nightscout/androidaps/queue/QueueThread.java diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.java b/app/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.java index eb04ce85fb..070189cb66 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.java @@ -16,12 +16,14 @@ public interface PumpInterface { boolean isInitialized(); boolean isSuspended(); boolean isBusy(); + boolean isConnected(); + boolean isConnecting(); + + void connect(String reason); + void disconnect(String reason); // Upload to pump new basal profile - int SUCCESS = 0; - int FAILED = 1; - int NOT_NEEDED = 2; - int setNewBasalProfile(Profile profile); + PumpEnactResult setNewBasalProfile(Profile profile); boolean isThisProfileSet(Profile profile); Date lastDataTime(); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/ConfigBuilder/ConfigBuilderPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/ConfigBuilder/ConfigBuilderPlugin.java index 12f1f46c1c..a2d513c8c5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/ConfigBuilder/ConfigBuilderPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/ConfigBuilder/ConfigBuilderPlugin.java @@ -52,6 +52,7 @@ import info.nightscout.androidaps.plugins.Overview.events.EventDismissBolusprogr import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.PumpVirtual.VirtualPumpPlugin; +import info.nightscout.androidaps.queue.CommandQueue; import info.nightscout.utils.NSUpload; import info.nightscout.utils.SP; @@ -79,6 +80,8 @@ public class ConfigBuilderPlugin implements PluginBase, PumpInterface, Constrain private PowerManager.WakeLock mWakeLock; + private static CommandQueue commandQueue = new CommandQueue(); + public ConfigBuilderPlugin() { MainApp.bus().register(this); PowerManager powerManager = (PowerManager) MainApp.instance().getApplicationContext().getSystemService(Context.POWER_SERVICE); @@ -197,6 +200,10 @@ public class ConfigBuilderPlugin implements PluginBase, PumpInterface, Constrain verifySelectionInCategories(); } + public static CommandQueue getCommandQueue() { + return commandQueue; + } + public static BgSourceInterface getActiveBgSource() { return activeBgSource; } @@ -384,7 +391,34 @@ public class ConfigBuilderPlugin implements PluginBase, PumpInterface, Constrain } @Override - public int setNewBasalProfile(Profile profile) { + public boolean isConnected() { + if (activePump != null) + return activePump.isConnected(); + return false; + } + + @Override + public boolean isConnecting() { + if (activePump != null) + return activePump.isConnecting(); + return false; + } + + @Override + public void connect(String reason) { + if (activePump != null) + activePump.connect(reason); + } + + @Override + public void disconnect(String reason) { + if (activePump != null) + activePump.disconnect(reason); + } + + @Override + public PumpEnactResult setNewBasalProfile(Profile profile) { + PumpEnactResult result = new PumpEnactResult(); // Compare with pump limits Profile.BasalValue[] basalValues = profile.getBasalValues(); @@ -392,7 +426,9 @@ public class ConfigBuilderPlugin implements PluginBase, PumpInterface, Constrain if (basalValues[index].value < 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)); - return FAILED; + result.success = false; + result.comment = MainApp.sResources.getString(R.string.basalvaluebelowminimum); + return result; } } @@ -400,11 +436,11 @@ public class ConfigBuilderPlugin implements PluginBase, PumpInterface, Constrain if (isThisProfileSet(profile)) { log.debug("Correct profile already set"); - return NOT_NEEDED; - } else if (activePump != null) { - return activePump.setNewBasalProfile(profile); + result.success = true; + result.enacted = false; + return result; } else - return SUCCESS; + return activePump.setNewBasalProfile(profile); } @Override diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileNS/NSProfilePlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileNS/NSProfilePlugin.java index 3476d3682d..dec14b344d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileNS/NSProfilePlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileNS/NSProfilePlugin.java @@ -121,14 +121,13 @@ public class NSProfilePlugin implements PluginBase, ProfileInterface { storeNSProfile(); MainApp.bus().post(new EventNSProfileUpdateGUI()); if (SP.getBoolean("syncprofiletopump", false)) { - if (ConfigBuilderPlugin.getActivePump().setNewBasalProfile(MainApp.getConfigBuilder().getProfile()) == PumpInterface.SUCCESS) { + if (ConfigBuilderPlugin.getActivePump().setNewBasalProfile(MainApp.getConfigBuilder().getProfile()).enacted == true) { SmsCommunicatorPlugin smsCommunicatorPlugin = MainApp.getSpecificPlugin(SmsCommunicatorPlugin.class); if (smsCommunicatorPlugin != null && smsCommunicatorPlugin.isEnabled(PluginBase.GENERAL)) { smsCommunicatorPlugin.sendNotificationToAllNumbers(MainApp.sResources.getString(R.string.profile_set_ok)); } } } - } private static void storeNSProfile() { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPlugin.java index f7e1c6b92f..cdcab62f48 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPlugin.java @@ -245,27 +245,35 @@ public class DanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, C // Pump interface @Override - public int setNewBasalProfile(Profile profile) { + public PumpEnactResult setNewBasalProfile(Profile profile) { + PumpEnactResult result = new PumpEnactResult(); + if (sExecutionService == null) { log.error("setNewBasalProfile sExecutionService is null"); - return FAILED; + result.comment = "setNewBasalProfile sExecutionService is null"; + return result; } if (!isInitialized()) { log.error("setNewBasalProfile not initialized"); Notification notification = new Notification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED, MainApp.sResources.getString(R.string.pumpNotInitializedProfileNotSet), Notification.URGENT); MainApp.bus().post(new EventNewNotification(notification)); - return FAILED; + result.comment = MainApp.sResources.getString(R.string.pumpNotInitializedProfileNotSet); + return result; } else { MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); } if (!sExecutionService.updateBasalsInPump(profile)) { Notification notification = new Notification(Notification.FAILED_UDPATE_PROFILE, MainApp.sResources.getString(R.string.failedupdatebasalprofile), Notification.URGENT); MainApp.bus().post(new EventNewNotification(notification)); - return FAILED; + result.comment = MainApp.sResources.getString(R.string.failedupdatebasalprofile); + return result; } else { MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); MainApp.bus().post(new EventDismissNotification(Notification.FAILED_UDPATE_PROFILE)); - return SUCCESS; + result.success = true; + result.enacted = true; + result.comment = "OK"; + return result; } } @@ -297,7 +305,7 @@ public class DanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, C @Override public void refreshDataFromPump(String reason) { if (!isConnected() && !isConnecting()) { - doConnect(reason); + connect(reason); } } @@ -350,7 +358,7 @@ public class DanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, C public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, boolean enforceNew) { // Recheck pump status if older than 30 min if (pump.lastConnection.getTime() + 30 * 60 * 1000L < System.currentTimeMillis()) { - doConnect("setTempBasalAbsolute old data"); + connect("setTempBasalAbsolute old data"); } PumpEnactResult result = new PumpEnactResult(); @@ -653,7 +661,8 @@ public class DanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, C } } - public static void doConnect(String from) { + @Override + public void connect(String from) { if (sExecutionService != null) { sExecutionService.connect(from); pumpDescription.basalStep = pump.basalStep; @@ -661,15 +670,18 @@ public class DanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, C } } - public static boolean isConnected() { + @Override + public boolean isConnected() { return sExecutionService != null && sExecutionService.isConnected(); } - public static boolean isConnecting() { + @Override + public boolean isConnecting() { return sExecutionService != null && sExecutionService.isConnecting(); } - public static void doDisconnect(String from) { + @Override + public void disconnect(String from) { if (sExecutionService != null) sExecutionService.disconnect(from); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/Dialogs/ProfileViewDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/Dialogs/ProfileViewDialog.java index fc7a9a2411..9554e18164 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/Dialogs/ProfileViewDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/Dialogs/ProfileViewDialog.java @@ -77,11 +77,11 @@ public class ProfileViewDialog extends DialogFragment { public void run() { DanaRPump.getInstance().lastSettingsRead = new Date(0); if (MainApp.getSpecificPlugin(DanaRPlugin.class).isEnabled(PluginBase.PUMP)) - DanaRPlugin.doConnect("ProfileViewDialog"); + DanaRPlugin.getPlugin().connect("ProfileViewDialog"); if (MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).isEnabled(PluginBase.PUMP)) - DanaRKoreanPlugin.doConnect("ProfileViewDialog"); + DanaRKoreanPlugin.getPlugin().connect("ProfileViewDialog"); if (MainApp.getSpecificPlugin(DanaRv2Plugin.class).isEnabled(PluginBase.PUMP)) - DanaRv2Plugin.doConnect("ProfileViewDialog"); + DanaRv2Plugin.getPlugin().connect("ProfileViewDialog"); } }); dismiss(); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgCheckValue.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgCheckValue.java index c05029caff..785e90650a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgCheckValue.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgCheckValue.java @@ -32,7 +32,7 @@ public class MsgCheckValue extends MessageBase { pump.productCode = intFromBuff(bytes, 2, 1); if (pump.model != DanaRPump.EXPORT_MODEL) { ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.wrongpumpdriverselected), R.raw.error); - ((DanaRPlugin) MainApp.getSpecificPlugin(DanaRPlugin.class)).doDisconnect("Wrong Model"); + ((DanaRPlugin) MainApp.getSpecificPlugin(DanaRPlugin.class)).disconnect("Wrong Model"); log.debug("Wrong model selected"); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgInitConnStatusTime.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgInitConnStatusTime.java index dfd5788ec9..2b2667f92c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgInitConnStatusTime.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgInitConnStatusTime.java @@ -26,7 +26,7 @@ public class MsgInitConnStatusTime extends MessageBase { public void handleMessage(byte[] bytes) { if (bytes.length - 10 > 7) { ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(),MainApp.sResources.getString(R.string.wrongpumpdriverselected), R.raw.error); - ((DanaRPlugin) MainApp.getSpecificPlugin(DanaRPlugin.class)).doDisconnect("Wrong Model"); + ((DanaRPlugin) MainApp.getSpecificPlugin(DanaRPlugin.class)).disconnect("Wrong Model"); log.debug("Wrong model selected. Switching to Korean DanaR"); MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).setFragmentEnabled(PluginBase.PUMP, true); MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).setFragmentVisible(PluginBase.PUMP, true); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/DanaRKoreanPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/DanaRKoreanPlugin.java index cda438aded..d8fde0e3ba 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/DanaRKoreanPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/DanaRKoreanPlugin.java @@ -247,27 +247,35 @@ public class DanaRKoreanPlugin implements PluginBase, PumpInterface, DanaRInterf // Pump interface @Override - public int setNewBasalProfile(Profile profile) { + public PumpEnactResult setNewBasalProfile(Profile profile) { + PumpEnactResult result = new PumpEnactResult(); + if (sExecutionService == null) { log.error("setNewBasalProfile sExecutionService is null"); - return FAILED; + result.comment = "setNewBasalProfile sExecutionService is null"; + return result; } if (!isInitialized()) { log.error("setNewBasalProfile not initialized"); Notification notification = new Notification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED, MainApp.sResources.getString(R.string.pumpNotInitializedProfileNotSet), Notification.URGENT); MainApp.bus().post(new EventNewNotification(notification)); - return FAILED; + result.comment = MainApp.sResources.getString(R.string.pumpNotInitializedProfileNotSet); + return result; } else { MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); } if (!sExecutionService.updateBasalsInPump(profile)) { Notification notification = new Notification(Notification.FAILED_UDPATE_PROFILE, MainApp.sResources.getString(R.string.failedupdatebasalprofile), Notification.URGENT); MainApp.bus().post(new EventNewNotification(notification)); - return FAILED; + result.comment = MainApp.sResources.getString(R.string.failedupdatebasalprofile); + return result; } else { MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); MainApp.bus().post(new EventDismissNotification(Notification.FAILED_UDPATE_PROFILE)); - return SUCCESS; + result.success = true; + result.enacted = true; + result.comment = "OK"; + return result; } } @@ -299,7 +307,7 @@ public class DanaRKoreanPlugin implements PluginBase, PumpInterface, DanaRInterf @Override public void refreshDataFromPump(String reason) { if (!isConnected() && !isConnecting()) { - doConnect(reason); + connect(reason); } } @@ -353,7 +361,7 @@ public class DanaRKoreanPlugin implements PluginBase, PumpInterface, DanaRInterf public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, boolean enforceNew) { // Recheck pump status if older than 30 min if (pump.lastConnection.getTime() + 30 * 60 * 1000L < System.currentTimeMillis()) { - doConnect("setTempBasalAbsolute old data"); + connect("setTempBasalAbsolute old data"); } PumpEnactResult result = new PumpEnactResult(); @@ -650,7 +658,8 @@ public class DanaRKoreanPlugin implements PluginBase, PumpInterface, DanaRInterf } } - public static void doConnect(String from) { + @Override + public void connect(String from) { if (sExecutionService != null) { sExecutionService.connect(from); pumpDescription.basalStep = pump.basalStep; @@ -658,15 +667,18 @@ public class DanaRKoreanPlugin implements PluginBase, PumpInterface, DanaRInterf } } - public static boolean isConnected() { + @Override + public boolean isConnected() { return sExecutionService != null && sExecutionService.isConnected(); } - public static boolean isConnecting() { + @Override + public boolean isConnecting() { return sExecutionService != null && sExecutionService.isConnecting(); } - public static void doDisconnect(String from) { + @Override + public void disconnect(String from) { if (sExecutionService != null) sExecutionService.disconnect(from); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/comm/MsgCheckValue_k.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/comm/MsgCheckValue_k.java index 7536bd278c..f048c07f20 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/comm/MsgCheckValue_k.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/comm/MsgCheckValue_k.java @@ -33,7 +33,7 @@ public class MsgCheckValue_k extends MessageBase { pump.productCode = intFromBuff(bytes, 2, 1); if (pump.model != DanaRPump.DOMESTIC_MODEL) { ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(),MainApp.sResources.getString(R.string.wrongpumpdriverselected), R.raw.error); - ((DanaRKoreanPlugin)MainApp.getSpecificPlugin(DanaRKoreanPlugin.class)).doDisconnect("Wrong Model"); + DanaRKoreanPlugin.getPlugin().disconnect("Wrong Model"); log.debug("Wrong model selected"); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/comm/MsgInitConnStatusTime_k.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/comm/MsgInitConnStatusTime_k.java index f54b8edb30..c72be877d4 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/comm/MsgInitConnStatusTime_k.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/comm/MsgInitConnStatusTime_k.java @@ -28,7 +28,7 @@ public class MsgInitConnStatusTime_k extends MessageBase { if (bytes.length - 10 < 10) { ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(),MainApp.sResources.getString(R.string.wrongpumpdriverselected), R.raw.error); - ((DanaRKoreanPlugin)MainApp.getSpecificPlugin(DanaRKoreanPlugin.class)).doDisconnect("Wrong Model"); + DanaRKoreanPlugin.getPlugin().disconnect("Wrong Model"); log.debug("Wrong model selected. Switching to export DanaR"); MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).setFragmentEnabled(PluginBase.PUMP, false); MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).setFragmentVisible(PluginBase.PUMP, false); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/CommandQueue.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/CommandQueue.java deleted file mode 100644 index d0651086ed..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/CommandQueue.java +++ /dev/null @@ -1,347 +0,0 @@ -package info.nightscout.androidaps.plugins.PumpDanaRS; - -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.utils.DecimalFormatter; - -/** - * Created by mike on 08.11.2017. - */ - -public class CommandQueue { - private static Logger log = LoggerFactory.getLogger(CommandQueue.class); - - enum CommandType { - BOLUS, - TEMPBASALPERCENT, - TEMPBASALABSOLUTE, - CANCELTEMPBASAL, - EXTENDEDBOLUS, - CANCELEXTENDEDBOLUS, - SETBASALPROFILE - } - - public class Command { - CommandType commandType; - Callback callback; - - int durationInMinutes; - - // Bolus - DetailedBolusInfo detailedBolusInfo; - // Temp basal percent - int percent; - // Temp basal absolute - double absoluteRate; - boolean enforceNew; - // Extended bolus - double insulin; - // Basal profile - Profile profile; - - public String status() { - switch (commandType) { - case BOLUS: - return "BOLUS " + DecimalFormatter.to1Decimal(detailedBolusInfo.insulin) + "U"; - case TEMPBASALPERCENT: - return "TEMPBASAL " + percent + "% " + durationInMinutes + " min"; - case TEMPBASALABSOLUTE: - return "TEMPBASAL " + absoluteRate + " U/h " + durationInMinutes + " min"; - case CANCELTEMPBASAL: - return "CANCEL TEMPBASAL"; - case EXTENDEDBOLUS: - return "EXTENDEDBOLUS " + insulin + " U " + durationInMinutes + " min"; - case CANCELEXTENDEDBOLUS: - return "CANCEL EXTENDEDBOLUS"; - case SETBASALPROFILE: - return "SETPROFILE"; - default: - return ""; - } - } - } - - public class Callback { - public PumpEnactResult result; - Runnable runnable; - - public Callback(Runnable runnable) { - this.runnable = runnable; - } - - public Callback result(PumpEnactResult result) { - this.result = result; - return this; - } - - public void run() { - runnable.run(); - } - } - - private LinkedList queue = new LinkedList<>(); - private Command performing; - - private PumpEnactResult executingNowError() { - PumpEnactResult result = new PumpEnactResult(); - result.success = false; - result.enacted = false; - result.comment = MainApp.sResources.getString(R.string.executingrightnow); - return result; - } - - public boolean isRunningTempBasal() { - if (performing != null) - if (performing.commandType == CommandType.TEMPBASALABSOLUTE || performing.commandType == CommandType.TEMPBASALPERCENT || performing.commandType == CommandType.CANCELTEMPBASAL) - return true; - return false; - } - - public boolean isRunningBolus() { - if (performing != null) - if (performing.commandType == CommandType.BOLUS) - return true; - return false; - } - - public boolean isRunningExtendedBolus() { - if (performing != null) - if (performing.commandType == CommandType.EXTENDEDBOLUS || performing.commandType == CommandType.CANCELEXTENDEDBOLUS) - return true; - return false; - } - - public boolean isRunningProfile() { - if (performing != null) - if (performing.commandType == CommandType.SETBASALPROFILE) - return true; - return false; - } - - private synchronized void removeAll(CommandType type) { - for (int i = 0; i < queue.size(); i++) { - Command c = queue.get(i); - switch (type) { - case TEMPBASALABSOLUTE: - case TEMPBASALPERCENT: - case CANCELTEMPBASAL: - if (c.commandType == CommandType.TEMPBASALABSOLUTE || c.commandType == CommandType.TEMPBASALPERCENT || c.commandType == CommandType.CANCELTEMPBASAL) { - queue.remove(i); - } - break; - case BOLUS: - if (c.commandType == CommandType.BOLUS) { - queue.remove(i); - } - break; - case EXTENDEDBOLUS: - case CANCELEXTENDEDBOLUS: - if (c.commandType == CommandType.EXTENDEDBOLUS || c.commandType == CommandType.CANCELEXTENDEDBOLUS) { - queue.remove(i); - } - break; - case SETBASALPROFILE: - if (c.commandType == CommandType.SETBASALPROFILE) { - queue.remove(i); - } - break; - } - } - } - - private synchronized void add(Command command) { - queue.add(command); - } - - private synchronized void pickup() { - performing = queue.poll(); - } - - public void clear() { - queue.clear(); - } - - private void notifyAboutNewCommand() { - - } - - // returns true if command is queued - public boolean bolus(DetailedBolusInfo detailedBolusInfo, Callback callback) { - if (isRunningBolus()) { - callback.result(executingNowError()).run(); - return false; - } - - // remove all unfinished boluese - removeAll(CommandType.BOLUS); - - // add new command to queue - Command command = new Command(); - command.commandType = CommandType.BOLUS; - command.detailedBolusInfo = detailedBolusInfo; - command.callback = callback; - add(command); - - notifyAboutNewCommand(); - - return true; - } - - // returns true if command is queued - public boolean tempBasalAbsolute(double absoluteRate, int durationInMinutes, boolean enforceNew, Callback callback) { - if (isRunningTempBasal()) { - callback.result(executingNowError()).run(); - return false; - } - - // remove all unfinished boluese - removeAll(CommandType.TEMPBASALABSOLUTE); - - // add new command to queue - Command command = new Command(); - command.commandType = CommandType.TEMPBASALABSOLUTE; - command.absoluteRate = absoluteRate; - command.durationInMinutes = durationInMinutes; - command.enforceNew = enforceNew; - command.callback = callback; - add(command); - - notifyAboutNewCommand(); - - return true; - } - - // returns true if command is queued - public boolean tempBasalPercent(int percent, int durationInMinutes, Callback callback) { - if (isRunningTempBasal()) { - callback.result(executingNowError()).run(); - return false; - } - - // remove all unfinished boluese - removeAll(CommandType.TEMPBASALPERCENT); - - // add new command to queue - Command command = new Command(); - command.commandType = CommandType.TEMPBASALPERCENT; - command.percent = percent; - command.durationInMinutes = durationInMinutes; - command.callback = callback; - add(command); - - notifyAboutNewCommand(); - - return true; - } - - // returns true if command is queued - public boolean extendedBolus(double insulin, int durationInMinutes, Callback callback) { - if (isRunningExtendedBolus()) { - callback.result(executingNowError()).run(); - return false; - } - - // remove all unfinished boluese - removeAll(CommandType.EXTENDEDBOLUS); - - // add new command to queue - Command command = new Command(); - command.commandType = CommandType.EXTENDEDBOLUS; - command.insulin = insulin; - command.durationInMinutes = durationInMinutes; - command.callback = callback; - add(command); - - notifyAboutNewCommand(); - - return true; - } - - // returns true if command is queued - public boolean cancelTempBasal(boolean enforceNew, Callback callback) { - if (isRunningTempBasal()) { - callback.result(executingNowError()).run(); - return false; - } - - // remove all unfinished boluese - removeAll(CommandType.CANCELTEMPBASAL); - - // add new command to queue - Command command = new Command(); - command.commandType = CommandType.CANCELTEMPBASAL; - command.enforceNew = enforceNew; - command.callback = callback; - add(command); - - notifyAboutNewCommand(); - - return true; - } - - // returns true if command is queued - public boolean cancelExtended(Callback callback) { - if (isRunningExtendedBolus()) { - callback.result(executingNowError()).run(); - return false; - } - - // remove all unfinished boluese - removeAll(CommandType.CANCELEXTENDEDBOLUS); - - // add new command to queue - Command command = new Command(); - command.commandType = CommandType.CANCELEXTENDEDBOLUS; - command.callback = callback; - add(command); - - notifyAboutNewCommand(); - - return true; - } - - // returns true if command is queued - public boolean setProfile(Profile profile, Callback callback) { - if (isRunningProfile()) { - callback.result(executingNowError()).run(); - return false; - } - - // remove all unfinished boluese - removeAll(CommandType.SETBASALPROFILE); - - // add new command to queue - Command command = new Command(); - command.commandType = CommandType.SETBASALPROFILE; - command.profile = profile; - command.callback = callback; - add(command); - - notifyAboutNewCommand(); - - return true; - } - - Spanned spannedStatus() { - String s = ""; - if (performing != null) { - s += "" + performing.status() + "
"; - } - for (int i = 0; i < queue.size(); i++) { - s += queue.get(i).status() + "
"; - } - return Html.fromHtml(s); - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/DanaRSPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/DanaRSPlugin.java index 916213c270..bab828696f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/DanaRSPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/DanaRSPlugin.java @@ -219,12 +219,13 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, mDeviceName = SP.getString(R.string.key_danars_name, ""); } - public static void connectIfNotConnected(String from) { + public void connectIfNotConnected(String from) { if (!isConnected()) connect(from); } - public static void connect(String from) { + @Override + public void connect(String from) { log.debug("RS connect from: " + from); if (danaRSService != null && !mDeviceAddress.equals("") && !mDeviceName.equals("")) { final Object o = new Object(); @@ -249,15 +250,18 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, } } - public static boolean isConnected() { + @Override + public boolean isConnected() { return danaRSService != null && danaRSService.isConnected(); } - public static boolean isConnecting() { + @Override + public boolean isConnecting() { return danaRSService != null && danaRSService.isConnecting(); } - public static void disconnect(String from) { + @Override + public void disconnect(String from) { if (danaRSService != null) danaRSService.disconnect(from); } @@ -383,30 +387,35 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, } @Override - public synchronized int setNewBasalProfile(Profile profile) { + public PumpEnactResult setNewBasalProfile(Profile profile) { + PumpEnactResult result = new PumpEnactResult(); + if (danaRSService == null) { log.error("setNewBasalProfile sExecutionService is null"); - return FAILED; + result.comment = "setNewBasalProfile sExecutionService is null"; + return result; } if (!isInitialized()) { log.error("setNewBasalProfile not initialized"); Notification notification = new Notification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED, MainApp.sResources.getString(R.string.pumpNotInitializedProfileNotSet), Notification.URGENT); MainApp.bus().post(new EventNewNotification(notification)); - return FAILED; + result.comment = MainApp.sResources.getString(R.string.pumpNotInitializedProfileNotSet); + return result; } else { MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); } - connectIfNotConnected("updateBasalsInPump"); if (!danaRSService.updateBasalsInPump(profile)) { Notification notification = new Notification(Notification.FAILED_UDPATE_PROFILE, MainApp.sResources.getString(R.string.failedupdatebasalprofile), Notification.URGENT); MainApp.bus().post(new EventNewNotification(notification)); - disconnect("SetNewBasalProfile"); - return FAILED; + result.comment = MainApp.sResources.getString(R.string.failedupdatebasalprofile); + return result; } else { MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); MainApp.bus().post(new EventDismissNotification(Notification.FAILED_UDPATE_PROFILE)); - disconnect("SetNewBasalProfile"); - return SUCCESS; + result.success = true; + result.enacted = true; + result.comment = "OK"; + return result; } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/BLEComm.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/BLEComm.java index 42f077887d..c2b11dbfb1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/BLEComm.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/BLEComm.java @@ -10,8 +10,6 @@ import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.Intent; -import android.os.Handler; -import android.os.HandlerThread; import android.os.SystemClock; import com.cozmo.danar.util.BleCommandUtil; @@ -33,7 +31,6 @@ import info.nightscout.androidaps.events.EventPumpStatusChanged; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump; import info.nightscout.androidaps.plugins.PumpDanaRS.DanaRSPlugin; import info.nightscout.androidaps.plugins.PumpDanaRS.activities.PairingHelperActivity; -import info.nightscout.androidaps.plugins.PumpDanaRS.activities.PairingProgressDialog; import info.nightscout.androidaps.plugins.PumpDanaRS.comm.DanaRSMessageHashTable; import info.nightscout.androidaps.plugins.PumpDanaRS.comm.DanaRS_Packet; import info.nightscout.androidaps.plugins.PumpDanaRS.events.EventDanaRSPacket; @@ -70,19 +67,6 @@ public class BLEComm { private DanaRS_Packet processsedMessage = null; private ArrayList mSendQueue = new ArrayList<>(); - // Variables for connection progress (elapsed time) - private Handler sHandler; - private HandlerThread sHandlerThread; - private long connectionStartTime = 0; - private final Runnable updateProgress = new Runnable() { - @Override - public void run() { - long secondsElapsed = (System.currentTimeMillis() - connectionStartTime) / 1000; - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.CONNECTING, (int) secondsElapsed)); - sHandler.postDelayed(updateProgress, 1000); - } - }; - private BluetoothManager mBluetoothManager = null; private BluetoothAdapter mBluetoothAdapter = null; private BluetoothDevice mBluetoothDevice = null; @@ -101,12 +85,6 @@ public class BLEComm { BLEComm(DanaRSService service) { this.service = service; initialize(); - - if (sHandlerThread == null) { - sHandlerThread = new HandlerThread(PairingProgressDialog.class.getSimpleName()); - sHandlerThread.start(); - sHandler = new Handler(sHandlerThread.getLooper()); - } } private boolean initialize() { @@ -160,9 +138,6 @@ public class BLEComm { return false; } - connectionStartTime = System.currentTimeMillis(); - - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.CONNECTING)); isConnecting = true; BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); @@ -171,7 +146,6 @@ public class BLEComm { return false; } - sHandler.post(updateProgress); mBluetoothGatt = device.connectGatt(service.getApplicationContext(), false, mGattCallback); setCharacteristicNotification(getUARTReadBTGattChar(), true); log.debug("Trying to create a new connection."); @@ -183,7 +157,6 @@ public class BLEComm { public void stopConnecting() { isConnecting = false; - sHandler.removeCallbacks(updateProgress); // just to be sure } public void disconnect(String from) { @@ -234,7 +207,6 @@ public class BLEComm { } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { close(); isConnected = false; - sHandler.removeCallbacks(updateProgress); // just to be sure MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTED)); log.debug("Device was disconnected " + gatt.getDevice().getName());//Device was disconnected } @@ -248,8 +220,6 @@ public class BLEComm { if (status == BluetoothGatt.GATT_SUCCESS) { findCharacteristic(); } - // stop sending connection progress - sHandler.removeCallbacks(updateProgress); SendPumpCheck(); // 1st message sent to pump after connect } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/DanaRv2Plugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/DanaRv2Plugin.java index f2ad2fd659..8aed0780d5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/DanaRv2Plugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/DanaRv2Plugin.java @@ -232,27 +232,35 @@ public class DanaRv2Plugin implements PluginBase, PumpInterface, DanaRInterface, // Pump interface @Override - public int setNewBasalProfile(Profile profile) { + public PumpEnactResult setNewBasalProfile(Profile profile) { + PumpEnactResult result = new PumpEnactResult(); + if (sExecutionService == null) { log.error("setNewBasalProfile sExecutionService is null"); - return FAILED; + result.comment = "setNewBasalProfile sExecutionService is null"; + return result; } if (!isInitialized()) { log.error("setNewBasalProfile not initialized"); Notification notification = new Notification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED, MainApp.sResources.getString(R.string.pumpNotInitializedProfileNotSet), Notification.URGENT); MainApp.bus().post(new EventNewNotification(notification)); - return FAILED; + result.comment = MainApp.sResources.getString(R.string.pumpNotInitializedProfileNotSet); + return result; } else { MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); } if (!sExecutionService.updateBasalsInPump(profile)) { Notification notification = new Notification(Notification.FAILED_UDPATE_PROFILE, MainApp.sResources.getString(R.string.failedupdatebasalprofile), Notification.URGENT); MainApp.bus().post(new EventNewNotification(notification)); - return FAILED; + result.comment = MainApp.sResources.getString(R.string.failedupdatebasalprofile); + return result; } else { MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); MainApp.bus().post(new EventDismissNotification(Notification.FAILED_UDPATE_PROFILE)); - return SUCCESS; + result.success = true; + result.enacted = true; + result.comment = "OK"; + return result; } } @@ -284,7 +292,7 @@ public class DanaRv2Plugin implements PluginBase, PumpInterface, DanaRInterface, @Override public void refreshDataFromPump(String reason) { if (!isConnected() && !isConnecting()) { - doConnect(reason); + connect(reason); } } @@ -361,7 +369,7 @@ public class DanaRv2Plugin implements PluginBase, PumpInterface, DanaRInterface, public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, boolean enforceNew) { // Recheck pump status if older than 30 min if (pump.lastConnection.getTime() + 30 * 60 * 1000L < System.currentTimeMillis()) { - doConnect("setTempBasalAbsolute old data"); + connect("setTempBasalAbsolute old data"); } PumpEnactResult result = new PumpEnactResult(); @@ -597,7 +605,8 @@ public class DanaRv2Plugin implements PluginBase, PumpInterface, DanaRInterface, } } - public static void doConnect(String from) { + @Override + public void connect(String from) { if (sExecutionService != null) { sExecutionService.connect(from); pumpDescription.basalStep = pump.basalStep; @@ -605,15 +614,18 @@ public class DanaRv2Plugin implements PluginBase, PumpInterface, DanaRInterface, } } - public static boolean isConnected() { + @Override + public boolean isConnected() { return sExecutionService != null && sExecutionService.isConnected(); } - public static boolean isConnecting() { + @Override + public boolean isConnecting() { return sExecutionService != null && sExecutionService.isConnecting(); } - public static void doDisconnect(String from) { + @Override + public void disconnect(String from) { if (sExecutionService != null) sExecutionService.disconnect(from); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/comm/MsgCheckValue_v2.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/comm/MsgCheckValue_v2.java index d7b37a4b0f..e31a1a3443 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/comm/MsgCheckValue_v2.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/comm/MsgCheckValue_v2.java @@ -37,13 +37,13 @@ public class MsgCheckValue_v2 extends MessageBase { pump.productCode = intFromBuff(bytes, 2, 1); if (pump.model != DanaRPump.EXPORT_MODEL) { ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.wrongpumpdriverselected), R.raw.error); - DanaRv2Plugin.doDisconnect("Wrong Model"); + DanaRv2Plugin.getPlugin().disconnect("Wrong Model"); log.debug("Wrong model selected"); } if (pump.protocol != 2) { ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(),MainApp.sResources.getString(R.string.wrongpumpdriverselected), R.raw.error); - DanaRKoreanPlugin.doDisconnect("Wrong Model"); + DanaRKoreanPlugin.getPlugin().disconnect("Wrong Model"); log.debug("Wrong model selected. Switching to non APS DanaR"); (MainApp.getSpecificPlugin(DanaRv2Plugin.class)).setFragmentEnabled(PluginBase.PUMP, false); (MainApp.getSpecificPlugin(DanaRv2Plugin.class)).setFragmentVisible(PluginBase.PUMP, false); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpMDI/MDIPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpMDI/MDIPlugin.java index da4ea8157d..f48f944886 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpMDI/MDIPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpMDI/MDIPlugin.java @@ -130,9 +130,29 @@ public class MDIPlugin implements PluginBase, PumpInterface { } @Override - public int setNewBasalProfile(Profile profile) { + public boolean isConnected() { + return true; + } + + @Override + public boolean isConnecting() { + return false; + } + + @Override + public void connect(String reason) { + } + + @Override + public void disconnect(String reason) { + } + + @Override + public PumpEnactResult setNewBasalProfile(Profile profile) { // Do nothing here. we are using MainApp.getConfigBuilder().getActiveProfile().getProfile(); - return SUCCESS; + PumpEnactResult result = new PumpEnactResult(); + result.success = true; + return result; } @Override diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpPlugin.java index 2e3be7ab95..06b979c52b 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpPlugin.java @@ -185,10 +185,30 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface { } @Override - public int setNewBasalProfile(Profile profile) { - // Do nothing here. we are using MainApp.getConfigBuilder().getActiveProfile().getProfile(); + public boolean isConnected() { + return true; + } + + @Override + public boolean isConnecting() { + return false; + } + + @Override + public void connect(String reason) { + } + + @Override + public void disconnect(String reason) { + } + + @Override + public PumpEnactResult setNewBasalProfile(Profile profile) { lastDataTime = new Date(); - return SUCCESS; + // Do nothing here. we are using MainApp.getConfigBuilder().getActiveProfile().getProfile(); + PumpEnactResult result = new PumpEnactResult(); + result.success = true; + return result; } @Override diff --git a/app/src/main/java/info/nightscout/androidaps/queue/Callback.java b/app/src/main/java/info/nightscout/androidaps/queue/Callback.java new file mode 100644 index 0000000000..a4b458ed43 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/queue/Callback.java @@ -0,0 +1,24 @@ +package info.nightscout.androidaps.queue; + +import info.nightscout.androidaps.data.PumpEnactResult; + +/** + * Created by mike on 09.11.2017. + */ +public class Callback { + public PumpEnactResult result; + Runnable runnable; + + public Callback(Runnable runnable) { + this.runnable = runnable; + } + + public Callback result(PumpEnactResult result) { + this.result = result; + return this; + } + + public void run() { + runnable.run(); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/queue/Command.java b/app/src/main/java/info/nightscout/androidaps/queue/Command.java new file mode 100644 index 0000000000..8971b1abf7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/queue/Command.java @@ -0,0 +1,21 @@ +package info.nightscout.androidaps.queue; + +/** + * Created by mike on 09.11.2017. + */ +public abstract class Command { + enum CommandType { + BOLUS, + TEMPBASAL, + EXTENDEDBOLUS, + BASALPROFILE, + READSTATUS + } + + CommandType commandType; + Callback callback; + + public abstract void execute(); + + public abstract String status(); +} diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandBolus.java b/app/src/main/java/info/nightscout/androidaps/queue/CommandBolus.java new file mode 100644 index 0000000000..ef9472c14e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/queue/CommandBolus.java @@ -0,0 +1,31 @@ +package info.nightscout.androidaps.queue; + +import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.utils.DecimalFormatter; + +/** + * Created by mike on 09.11.2017. + */ + +public class CommandBolus extends Command { + DetailedBolusInfo detailedBolusInfo; + + CommandBolus(DetailedBolusInfo detailedBolusInfo, Callback callback) { + commandType = CommandType.BOLUS; + this.detailedBolusInfo = detailedBolusInfo; + this.callback = callback; + } + + @Override + public void execute() { + PumpEnactResult r = ConfigBuilderPlugin.getActivePump().deliverTreatment(detailedBolusInfo); + if (callback != null) + callback.result(r).run(); + } + + public String status() { + return "BOLUS " + DecimalFormatter.to1Decimal(detailedBolusInfo.insulin) + "U"; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandCancelExtendedBolus.java b/app/src/main/java/info/nightscout/androidaps/queue/CommandCancelExtendedBolus.java new file mode 100644 index 0000000000..4c50ab04b3 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/queue/CommandCancelExtendedBolus.java @@ -0,0 +1,28 @@ +package info.nightscout.androidaps.queue; + +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; + +/** + * Created by mike on 09.11.2017. + */ + +public class CommandCancelExtendedBolus extends Command { + + public CommandCancelExtendedBolus(Callback callback) { + commandType = CommandType.EXTENDEDBOLUS; + this.callback = callback; + } + + @Override + public void execute() { + PumpEnactResult r = ConfigBuilderPlugin.getActivePump().cancelExtendedBolus(); + if (callback != null) + callback.result(r).run(); + } + + @Override + public String status() { + return "CANCEL EXTENDEDBOLUS"; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandCancelTempBasal.java b/app/src/main/java/info/nightscout/androidaps/queue/CommandCancelTempBasal.java new file mode 100644 index 0000000000..dce8f53784 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/queue/CommandCancelTempBasal.java @@ -0,0 +1,30 @@ +package info.nightscout.androidaps.queue; + +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; + +/** + * Created by mike on 09.11.2017. + */ + +public class CommandCancelTempBasal extends Command { + boolean enforceNew; + + CommandCancelTempBasal(boolean enforceNew, Callback callback) { + commandType = CommandType.TEMPBASAL; + this.enforceNew = enforceNew; + this.callback = callback; + } + + @Override + public void execute() { + PumpEnactResult r = ConfigBuilderPlugin.getActivePump().cancelTempBasal(enforceNew); + if (callback != null) + callback.result(r).run(); + } + + @Override + public String status() { + return "CANCEL TEMPBASAL"; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandExtendedBolus.java b/app/src/main/java/info/nightscout/androidaps/queue/CommandExtendedBolus.java new file mode 100644 index 0000000000..85b3c70315 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/queue/CommandExtendedBolus.java @@ -0,0 +1,32 @@ +package info.nightscout.androidaps.queue; + +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; + +/** + * Created by mike on 09.11.2017. + */ + +public class CommandExtendedBolus extends Command { + double insulin; + int durationInMinutes; + + public CommandExtendedBolus(double insulin, int durationInMinutes, Callback callback) { + commandType = CommandType.EXTENDEDBOLUS; + this.insulin = insulin; + this.durationInMinutes = durationInMinutes; + this.callback = callback; + } + + @Override + public void execute() { + PumpEnactResult r = ConfigBuilderPlugin.getActivePump().setExtendedBolus(insulin, durationInMinutes); + if (callback != null) + callback.result(r).run(); + } + + @Override + public String status() { + return "EXTENDEDBOLUS " + insulin + " U " + durationInMinutes + " min"; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java new file mode 100644 index 0000000000..35b823f38d --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java @@ -0,0 +1,235 @@ +package info.nightscout.androidaps.queue; + +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; + +/** + * Created by mike on 08.11.2017. + */ + +public class CommandQueue { + private static Logger log = LoggerFactory.getLogger(CommandQueue.class); + + private LinkedList queue = new LinkedList<>(); + private Command performing; + + private QueueThread thread = new QueueThread(this); + + private PumpEnactResult executingNowError() { + PumpEnactResult result = new PumpEnactResult(); + result.success = false; + result.enacted = false; + result.comment = MainApp.sResources.getString(R.string.executingrightnow); + return result; + } + + public boolean isRunningTempBasal() { + if (performing != null && performing.commandType == Command.CommandType.TEMPBASAL) + return true; + return false; + } + + public boolean isRunningBolus() { + if (performing != null && performing.commandType == Command.CommandType.BOLUS) + return true; + return false; + } + + public boolean isRunningExtendedBolus() { + if (performing != null && performing.commandType == Command.CommandType.EXTENDEDBOLUS) + return true; + return false; + } + + public boolean isRunningProfile() { + if (performing != null && performing.commandType == Command.CommandType.BASALPROFILE) + 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); + } + } + } + + private synchronized void add(Command command) { + queue.add(command); + } + + protected synchronized void pickup() { + performing = queue.poll(); + } + + public void clear() { + queue.clear(); + } + + public int size() { + return queue.size(); + } + + public Command performing() { + return performing; + } + + public void resetPerforming() { + performing = null; + } + + private void notifyAboutNewCommand() { + if (!thread.isAlive()) + thread.start(); + } + + // returns true if command is queued + public boolean bolus(DetailedBolusInfo detailedBolusInfo, Callback callback) { + if (isRunningBolus()) { + callback.result(executingNowError()).run(); + return false; + } + + // remove all unfinished boluses + removeAll(Command.CommandType.BOLUS); + + // add new command to queue + add(new CommandBolus(detailedBolusInfo, callback)); + + notifyAboutNewCommand(); + + return true; + } + + // returns true if command is queued + public boolean tempBasalAbsolute(double absoluteRate, int durationInMinutes, boolean enforceNew, Callback callback) { + if (isRunningTempBasal()) { + callback.result(executingNowError()).run(); + return false; + } + + // remove all unfinished boluese + removeAll(Command.CommandType.TEMPBASAL); + + // add new command to queue + add(new CommandTempBasalAbsolute(absoluteRate, durationInMinutes, enforceNew, callback)); + + notifyAboutNewCommand(); + + return true; + } + + // returns true if command is queued + public boolean tempBasalPercent(int percent, int durationInMinutes, Callback callback) { + if (isRunningTempBasal()) { + callback.result(executingNowError()).run(); + return false; + } + + // remove all unfinished boluese + removeAll(Command.CommandType.TEMPBASAL); + + // add new command to queue + add(new CommandTempBasalPercent(percent, durationInMinutes, callback)); + + notifyAboutNewCommand(); + + return true; + } + + // returns true if command is queued + public boolean extendedBolus(double insulin, int durationInMinutes, Callback callback) { + if (isRunningExtendedBolus()) { + callback.result(executingNowError()).run(); + return false; + } + + // remove all unfinished boluese + removeAll(Command.CommandType.EXTENDEDBOLUS); + + // add new command to queue + add(new CommandExtendedBolus(insulin, durationInMinutes, callback)); + + notifyAboutNewCommand(); + + return true; + } + + // returns true if command is queued + public boolean cancelTempBasal(boolean enforceNew, Callback callback) { + if (isRunningTempBasal()) { + callback.result(executingNowError()).run(); + return false; + } + + // remove all unfinished boluese + 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) { + if (isRunningExtendedBolus()) { + callback.result(executingNowError()).run(); + return false; + } + + // remove all unfinished boluese + 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 (isRunningProfile()) { + callback.result(executingNowError()).run(); + return false; + } + + // remove all unfinished boluese + removeAll(Command.CommandType.BASALPROFILE); + + // add new command to queue + add(new CommandSetProfile(profile, callback)); + + notifyAboutNewCommand(); + + return true; + } + + Spanned spannedStatus() { + String s = ""; + if (performing != null) { + s += "" + performing.status() + "
"; + } + for (int i = 0; i < queue.size(); i++) { + s += queue.get(i).status() + "
"; + } + return Html.fromHtml(s); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandReadStatus.java b/app/src/main/java/info/nightscout/androidaps/queue/CommandReadStatus.java new file mode 100644 index 0000000000..e8ceb84091 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/queue/CommandReadStatus.java @@ -0,0 +1,25 @@ +package info.nightscout.androidaps.queue; + +/** + * Created by mike on 09.11.2017. + */ + +public class CommandReadStatus extends Command { + + CommandReadStatus(Callback callback) { + commandType = CommandType.READSTATUS; + this.callback = callback; + } + + @Override + public void execute() { + // do nothing by default. Status is read on connection + if (callback != null) + callback.result(null).run(); + } + + @Override + public String status() { + return "READ STATUS"; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandSetProfile.java b/app/src/main/java/info/nightscout/androidaps/queue/CommandSetProfile.java new file mode 100644 index 0000000000..273e3b344c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/queue/CommandSetProfile.java @@ -0,0 +1,31 @@ +package info.nightscout.androidaps.queue; + +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; + +/** + * Created by mike on 09.11.2017. + */ + +public class CommandSetProfile extends Command { + Profile profile; + + CommandSetProfile(Profile profile, Callback callback) { + commandType = CommandType.BASALPROFILE; + this.profile = profile; + this.callback = callback; + } + + @Override + public void execute() { + PumpEnactResult r = ConfigBuilderPlugin.getActivePump().setNewBasalProfile(profile); + if (callback != null) + callback.result(r).run(); + } + + @Override + public String status() { + return "SETPROFILE"; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandTempBasalAbsolute.java b/app/src/main/java/info/nightscout/androidaps/queue/CommandTempBasalAbsolute.java new file mode 100644 index 0000000000..136efe72be --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/queue/CommandTempBasalAbsolute.java @@ -0,0 +1,34 @@ +package info.nightscout.androidaps.queue; + +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; + +/** + * Created by mike on 09.11.2017. + */ + +public class CommandTempBasalAbsolute extends Command { + int durationInMinutes; + double absoluteRate; + boolean enforceNew; + + CommandTempBasalAbsolute(double absoluteRate, int durationInMinutes, boolean enforceNew, Callback callback) { + commandType = CommandType.TEMPBASAL; + this.absoluteRate = absoluteRate; + this.durationInMinutes = durationInMinutes; + this.enforceNew = enforceNew; + this.callback = callback; + } + + @Override + public void execute() { + PumpEnactResult r = ConfigBuilderPlugin.getActivePump().setTempBasalAbsolute(absoluteRate, durationInMinutes, enforceNew); + if (callback != null) + callback.result(r).run(); + } + + @Override + public String status() { + return "TEMPBASAL " + absoluteRate + " U/h " + durationInMinutes + " min"; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandTempBasalPercent.java b/app/src/main/java/info/nightscout/androidaps/queue/CommandTempBasalPercent.java new file mode 100644 index 0000000000..a645f7a93a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/queue/CommandTempBasalPercent.java @@ -0,0 +1,32 @@ +package info.nightscout.androidaps.queue; + +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; + +/** + * Created by mike on 09.11.2017. + */ + +public class CommandTempBasalPercent extends Command { + int durationInMinutes; + int percent; + + CommandTempBasalPercent(int percent, int durationInMinutes, Callback callback) { + commandType = CommandType.TEMPBASAL; + this.percent = percent; + this.durationInMinutes = durationInMinutes; + this.callback = callback; + } + + @Override + public void execute() { + PumpEnactResult r = ConfigBuilderPlugin.getActivePump().setTempBasalPercent(percent, durationInMinutes); + if (callback != null) + callback.result(r).run(); + } + + @Override + public String status() { + return "TEMPBASAL " + percent + "% " + durationInMinutes + " min"; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.java b/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.java new file mode 100644 index 0000000000..41c9f42434 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.java @@ -0,0 +1,69 @@ +package info.nightscout.androidaps.queue; + +import android.os.SystemClock; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.events.EventPumpStatusChanged; +import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; + +/** + * Created by mike on 09.11.2017. + */ + +public class QueueThread extends Thread { + private static Logger log = LoggerFactory.getLogger(QueueThread.class); + + CommandQueue queue; + boolean keepRunning = false; + + + private long connectionStartTime = 0; + + public QueueThread(CommandQueue queue) { + super(QueueThread.class.toString()); + + this.queue = queue; + keepRunning = true; + } + + @Override + public final void run() { + PumpInterface pump = ConfigBuilderPlugin.getActivePump(); + + while (keepRunning) { + if (pump.isConnecting()) { + long secondsElapsed = (System.currentTimeMillis() - connectionStartTime) / 1000; + MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.CONNECTING, (int) secondsElapsed)); + SystemClock.sleep(1000); + } + + if (!pump.isConnected()) { + MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.CONNECTING)); + pump.connect("Not connected"); + connectionStartTime = System.currentTimeMillis(); + SystemClock.sleep(1000); + } + + if (queue.performing() == null) { + // Pickup 1st command and set performing variable + if (queue.size() > 0) { + queue.pickup(); + queue.performing().execute(); + queue.resetPerforming(); + } + + } + + if (queue.size() == 0 && queue.performing() == null) { + pump.disconnect("Queue empty"); + keepRunning = false; + } + } + } + + +} From 86b11edd58621351e394138bf01fbe337aa69429 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Sat, 11 Nov 2017 14:05:29 +0100 Subject: [PATCH 04/14] RS connection and status reading --- .../info/nightscout/androidaps/Constants.java | 3 + .../info/nightscout/androidaps/MainApp.java | 9 +- .../androidaps/interfaces/DanaRInterface.java | 4 +- .../androidaps/interfaces/PumpInterface.java | 4 +- .../ConfigBuilder/ConfigBuilderPlugin.java | 18 +- .../plugins/Overview/OverviewFragment.java | 7 +- .../plugins/PumpDanaR/DanaRFragment.java | 64 ++++--- .../plugins/PumpDanaR/DanaRPlugin.java | 19 +- .../services/DanaRExecutionService.java | 10 +- .../PumpDanaRKorean/DanaRKoreanPlugin.java | 19 +- .../services/DanaRKoreanExecutionService.java | 10 +- .../plugins/PumpDanaRS/DanaRSPlugin.java | 69 ++----- .../DanaRS_Packet_APS_History_Events.java | 5 +- .../plugins/PumpDanaRS/services/BLEComm.java | 41 +---- .../PumpDanaRS/services/DanaRSService.java | 18 +- .../plugins/PumpDanaRv2/DanaRv2Plugin.java | 19 +- .../services/DanaRv2ExecutionService.java | 10 +- .../androidaps/plugins/PumpMDI/MDIPlugin.java | 13 +- .../PumpVirtual/VirtualPumpPlugin.java | 18 +- .../nightscout/androidaps/queue/Command.java | 21 --- .../androidaps/queue/CommandQueue.java | 174 +++++++++++++----- .../androidaps/queue/QueueThread.java | 41 ++++- .../androidaps/queue/commands/Command.java | 35 ++++ .../queue/{ => commands}/CommandBolus.java | 9 +- .../CommandCancelExtendedBolus.java | 7 +- .../CommandCancelTempBasal.java | 9 +- .../{ => commands}/CommandExtendedBolus.java | 11 +- .../queue/commands/CommandLoadHistory.java | 38 ++++ .../{ => commands}/CommandReadStatus.java | 13 +- .../{ => commands}/CommandSetProfile.java | 9 +- .../CommandTempBasalAbsolute.java | 9 +- .../CommandTempBasalPercent.java | 9 +- .../queue/events/EventQueueChanged.java | 8 + .../receivers/KeepAliveReceiver.java | 24 +-- app/src/main/res/layout/danar_fragment.xml | 7 + 35 files changed, 460 insertions(+), 324 deletions(-) delete mode 100644 app/src/main/java/info/nightscout/androidaps/queue/Command.java create mode 100644 app/src/main/java/info/nightscout/androidaps/queue/commands/Command.java rename app/src/main/java/info/nightscout/androidaps/queue/{ => commands}/CommandBolus.java (67%) rename app/src/main/java/info/nightscout/androidaps/queue/{ => commands}/CommandCancelExtendedBolus.java (69%) rename app/src/main/java/info/nightscout/androidaps/queue/{ => commands}/CommandCancelTempBasal.java (62%) rename app/src/main/java/info/nightscout/androidaps/queue/{ => commands}/CommandExtendedBolus.java (68%) create mode 100644 app/src/main/java/info/nightscout/androidaps/queue/commands/CommandLoadHistory.java rename app/src/main/java/info/nightscout/androidaps/queue/{ => commands}/CommandReadStatus.java (50%) rename app/src/main/java/info/nightscout/androidaps/queue/{ => commands}/CommandSetProfile.java (64%) rename app/src/main/java/info/nightscout/androidaps/queue/{ => commands}/CommandTempBasalAbsolute.java (64%) rename app/src/main/java/info/nightscout/androidaps/queue/{ => commands}/CommandTempBasalPercent.java (64%) create mode 100644 app/src/main/java/info/nightscout/androidaps/queue/events/EventQueueChanged.java diff --git a/app/src/main/java/info/nightscout/androidaps/Constants.java b/app/src/main/java/info/nightscout/androidaps/Constants.java index e37a5b4b9f..d643d1a82d 100644 --- a/app/src/main/java/info/nightscout/androidaps/Constants.java +++ b/app/src/main/java/info/nightscout/androidaps/Constants.java @@ -58,4 +58,7 @@ public class Constants { //Autosens public static final double DEVIATION_TO_BE_EQUAL = 2.0; + + // Pump + public static final int PUMP_MAX_CONNECTION_TIME_IN_SECONDS = 5 * 60; } diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.java b/app/src/main/java/info/nightscout/androidaps/MainApp.java index cafeec6dd6..29d4f30f0d 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainApp.java +++ b/app/src/main/java/info/nightscout/androidaps/MainApp.java @@ -168,16 +168,13 @@ public class MainApp extends Application { startKeepAliveService(); - Thread t = new Thread(new Runnable() { + new Thread(new Runnable() { @Override public void run() { SystemClock.sleep(5000); - PumpInterface pump = MainApp.getConfigBuilder(); - if (pump != null) - pump.refreshDataFromPump("Initialization"); + ConfigBuilderPlugin.getCommandQueue().readStatus("Initialization", null); } - }); - t.start(); + }).start(); } diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/DanaRInterface.java b/app/src/main/java/info/nightscout/androidaps/interfaces/DanaRInterface.java index abd1d8e304..7f6d8e9627 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/DanaRInterface.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/DanaRInterface.java @@ -1,9 +1,11 @@ package info.nightscout.androidaps.interfaces; +import info.nightscout.androidaps.data.PumpEnactResult; + /** * Created by mike on 12.06.2017. */ public interface DanaRInterface { - boolean loadHistory(byte type); + PumpEnactResult loadHistory(byte type); } diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.java b/app/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.java index 070189cb66..a3ada67538 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.java @@ -21,13 +21,15 @@ public interface PumpInterface { void connect(String reason); void disconnect(String reason); + void stopConnecting(); + + void getPumpStatus(); // Upload to pump new basal profile PumpEnactResult setNewBasalProfile(Profile profile); boolean isThisProfileSet(Profile profile); Date lastDataTime(); - void refreshDataFromPump(String reason); double getBaseBasalRate(); // base basal rate, not temp basal diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/ConfigBuilder/ConfigBuilderPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/ConfigBuilder/ConfigBuilderPlugin.java index a2d513c8c5..384fa7f992 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/ConfigBuilder/ConfigBuilderPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/ConfigBuilder/ConfigBuilderPlugin.java @@ -416,6 +416,18 @@ public class ConfigBuilderPlugin implements PluginBase, PumpInterface, Constrain activePump.disconnect(reason); } + @Override + public void stopConnecting() { + if (activePump != null) + activePump.stopConnecting(); + } + + @Override + public void getPumpStatus() { + if (activePump != null) + activePump.getPumpStatus(); + } + @Override public PumpEnactResult setNewBasalProfile(Profile profile) { PumpEnactResult result = new PumpEnactResult(); @@ -457,12 +469,6 @@ public class ConfigBuilderPlugin implements PluginBase, PumpInterface, Constrain else return new Date(); } - @Override - public void refreshDataFromPump(String reason) { - if (activePump != null) - activePump.refreshDataFromPump(reason); - } - @Override public double getBaseBasalRate() { if (activePump != null) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/OverviewFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/OverviewFragment.java index 67ab9227ed..339132adc1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/OverviewFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/OverviewFragment.java @@ -601,12 +601,7 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, break; case R.id.overview_pumpstatus: if (MainApp.getConfigBuilder().isSuspended() || !MainApp.getConfigBuilder().isInitialized()) - sHandler.post(new Runnable() { - @Override - public void run() { - MainApp.getConfigBuilder().refreshDataFromPump("RefreshClicked"); - } - }); + ConfigBuilderPlugin.getCommandQueue().readStatus("RefreshClicked", null); break; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRFragment.java index d48013b0e6..6de7c23eca 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRFragment.java @@ -8,6 +8,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.support.v4.app.FragmentManager; +import android.text.Spanned; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -27,10 +28,12 @@ import info.nightscout.androidaps.events.EventExtendedBolusChange; import info.nightscout.androidaps.events.EventPumpStatusChanged; import info.nightscout.androidaps.events.EventTempBasalChange; import info.nightscout.androidaps.plugins.Common.SubscriberFragment; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.PumpDanaR.Dialogs.ProfileViewDialog; import info.nightscout.androidaps.plugins.PumpDanaR.activities.DanaRHistoryActivity; import info.nightscout.androidaps.plugins.PumpDanaR.activities.DanaRStatsActivity; import info.nightscout.androidaps.plugins.PumpDanaR.events.EventDanaRNewStatus; +import info.nightscout.androidaps.queue.events.EventQueueChanged; import info.nightscout.utils.DateUtil; import info.nightscout.utils.DecimalFormatter; import info.nightscout.utils.SetWarnColor; @@ -38,11 +41,14 @@ import info.nightscout.utils.SetWarnColor; public class DanaRFragment extends SubscriberFragment { private static Logger log = LoggerFactory.getLogger(DanaRFragment.class); - private static Handler sHandler; - private static HandlerThread sHandlerThread; - private Handler loopHandler = new Handler(); - private Runnable refreshLoop = null; + private Runnable refreshLoop = new Runnable() { + @Override + public void run() { + updateGUI(); + loopHandler.postDelayed(refreshLoop, 60 * 1000L); + } + }; TextView lastConnectionView; TextView btConnectionView; @@ -58,6 +64,7 @@ public class DanaRFragment extends SubscriberFragment { TextView basalStepView; TextView bolusStepView; TextView serialNumberView; + TextView queueView; Button viewProfileButton; Button historyButton; Button statsButton; @@ -65,35 +72,19 @@ public class DanaRFragment extends SubscriberFragment { LinearLayout pumpStatusLayout; TextView pumpStatusView; - static Runnable connectRunnable = new Runnable() { - @Override - public void run() { - MainApp.getConfigBuilder().refreshDataFromPump("Connect request from GUI"); - } - }; - - public DanaRFragment() { - if (sHandlerThread == null) { - sHandlerThread = new HandlerThread(DanaRFragment.class.getSimpleName()); - sHandlerThread.start(); - sHandler = new Handler(sHandlerThread.getLooper()); - } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (refreshLoop == null) { - refreshLoop = new Runnable() { - @Override - public void run() { - updateGUI(); - loopHandler.postDelayed(refreshLoop, 60 * 1000L); - } - }; - loopHandler.postDelayed(refreshLoop, 60 * 1000L); - } + loopHandler.postDelayed(refreshLoop, 60 * 1000L); + } + + @Override + public void onDestroy() { + super.onDestroy(); + loopHandler.removeCallbacks(refreshLoop); } @Override @@ -118,6 +109,7 @@ public class DanaRFragment extends SubscriberFragment { basalStepView = (TextView) view.findViewById(R.id.danar_basalstep); bolusStepView = (TextView) view.findViewById(R.id.danar_bolusstep); serialNumberView = (TextView) view.findViewById(R.id.danar_serialnumber); + queueView = (TextView) view.findViewById(R.id.danar_queue); pumpStatusView = (TextView) view.findViewById(R.id.overview_pumpstatus); pumpStatusView.setBackgroundColor(MainApp.sResources.getColor(R.color.colorInitializingBorder)); @@ -150,7 +142,7 @@ public class DanaRFragment extends SubscriberFragment { @Override public void onClick(View v) { log.debug("Clicked connect to pump"); - sHandler.post(connectRunnable); + ConfigBuilderPlugin.getCommandQueue().readStatus("Clicked connect to pump", null); } }); @@ -206,6 +198,11 @@ public class DanaRFragment extends SubscriberFragment { updateGUI(); } + @Subscribe + public void onStatusEvent(final EventQueueChanged s) { + updateGUI(); + } + // GUI functions @Override protected void updateGUI() { @@ -266,10 +263,17 @@ public class DanaRFragment extends SubscriberFragment { basalStepView.setText("" + pump.basalStep); bolusStepView.setText("" + pump.bolusStep); serialNumberView.setText("" + pump.serialNumber); - + if (queueView != null) { + Spanned status = ConfigBuilderPlugin.getCommandQueue().spannedStatus(); + if (status.toString().equals("")) { + queueView.setVisibility(View.GONE); + } else { + queueView.setVisibility(View.VISIBLE); + queueView.setText(status); + } + } } }); - } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPlugin.java index cdcab62f48..4866cc5d5c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPlugin.java @@ -302,13 +302,6 @@ public class DanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, C return pump.lastConnection; } - @Override - public void refreshDataFromPump(String reason) { - if (!isConnected() && !isConnecting()) { - connect(reason); - } - } - @Override public double getBaseBasalRate() { return pump.currentBasal; @@ -685,6 +678,16 @@ public class DanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, C if (sExecutionService != null) sExecutionService.disconnect(from); } + @Override + public void stopConnecting() { + // TODO AAAAAAAAAAAAAAAAAAAAAAAAAAA + } + + @Override + public void getPumpStatus() { + // TODO AAAAAAAAAAAAAAAAAAAAAAAAAAA + } + @Override public JSONObject getJSONStatus() { if (pump.lastConnection.getTime() + 5 * 60 * 1000L < System.currentTimeMillis()) { @@ -748,7 +751,7 @@ public class DanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, C */ @Override - public boolean loadHistory(byte type) { + public PumpEnactResult loadHistory(byte type) { return sExecutionService.loadHistory(type); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/services/DanaRExecutionService.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/services/DanaRExecutionService.java index 1ef5349f25..4293480723 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/services/DanaRExecutionService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/services/DanaRExecutionService.java @@ -27,6 +27,7 @@ import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.db.Treatment; import info.nightscout.androidaps.events.EventAppExit; import info.nightscout.androidaps.events.EventInitializationChanged; @@ -513,9 +514,10 @@ public class DanaRExecutionService extends Service { return true; } - public boolean loadHistory(byte type) { + public PumpEnactResult loadHistory(byte type) { + PumpEnactResult result = new PumpEnactResult(); connect("loadHistory"); - if (!isConnected()) return false; + if (!isConnected()) return result; MessageBase msg = null; switch (type) { case RecordTypes.RECORD_TYPE_ALARM: @@ -555,7 +557,9 @@ public class DanaRExecutionService extends Service { } waitMsec(200); mSerialIOThread.sendMessage(new MsgPCCommStop()); - return true; + result.success = true; + result.comment = "OK"; + return result; } public boolean updateBasalsInPump(final Profile profile) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/DanaRKoreanPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/DanaRKoreanPlugin.java index d8fde0e3ba..1c65b55374 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/DanaRKoreanPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/DanaRKoreanPlugin.java @@ -304,13 +304,6 @@ public class DanaRKoreanPlugin implements PluginBase, PumpInterface, DanaRInterf return pump.lastConnection; } - @Override - public void refreshDataFromPump(String reason) { - if (!isConnected() && !isConnecting()) { - connect(reason); - } - } - @Override public double getBaseBasalRate() { return pump.currentBasal; @@ -682,6 +675,16 @@ public class DanaRKoreanPlugin implements PluginBase, PumpInterface, DanaRInterf if (sExecutionService != null) sExecutionService.disconnect(from); } + @Override + public void stopConnecting() { + // TODO AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + } + + @Override + public void getPumpStatus() { + // TODO AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + } + @Override public JSONObject getJSONStatus() { if (pump.lastConnection.getTime() + 5 * 60 * 1000L < System.currentTimeMillis()) { @@ -745,7 +748,7 @@ public class DanaRKoreanPlugin implements PluginBase, PumpInterface, DanaRInterf */ @Override - public boolean loadHistory(byte type) { + public PumpEnactResult loadHistory(byte type) { return sExecutionService.loadHistory(type); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/services/DanaRKoreanExecutionService.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/services/DanaRKoreanExecutionService.java index f77c03e337..f0786a91be 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/services/DanaRKoreanExecutionService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/services/DanaRKoreanExecutionService.java @@ -27,6 +27,7 @@ import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.db.Treatment; import info.nightscout.androidaps.events.EventAppExit; import info.nightscout.androidaps.events.EventInitializationChanged; @@ -458,9 +459,10 @@ public class DanaRKoreanExecutionService extends Service { return true; } - public boolean loadHistory(byte type) { + public PumpEnactResult loadHistory(byte type) { + PumpEnactResult result = new PumpEnactResult(); connect("loadHistory"); - if (!isConnected()) return false; + if (!isConnected()) return result; MessageBase msg = null; switch (type) { case RecordTypes.RECORD_TYPE_ALARM: @@ -500,7 +502,9 @@ public class DanaRKoreanExecutionService extends Service { } waitMsec(200); mSerialIOThread.sendMessage(new MsgPCCommStop()); - return true; + result.success = true; + result.comment = "OK"; + return result; } public boolean updateBasalsInPump(final Profile profile) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/DanaRSPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/DanaRSPlugin.java index bab828696f..2e3952e4f6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/DanaRSPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/DanaRSPlugin.java @@ -219,11 +219,6 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, mDeviceName = SP.getString(R.string.key_danars_name, ""); } - public void connectIfNotConnected(String from) { - if (!isConnected()) - connect(from); - } - @Override public void connect(String from) { log.debug("RS connect from: " + from); @@ -231,22 +226,8 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, final Object o = new Object(); danaRSService.connect(from, mDeviceAddress, o); - synchronized (o) { - try { - o.wait(20000); - } catch (InterruptedException e) { - log.error("InterruptedException " + e); - } - } pumpDescription.basalStep = pump.basalStep; pumpDescription.bolusStep = pump.bolusStep; - if (isConnected()) - log.debug("RS connected: " + from); - else { - MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.connectiontimedout))); - danaRSService.stopConnecting(); - log.debug("RS connect failed from: " + from); - } } } @@ -265,19 +246,23 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, if (danaRSService != null) danaRSService.disconnect(from); } - public static void sendMessage(DanaRS_Packet message) { - if (danaRSService != null) danaRSService.sendMessage(message); + @Override + public void stopConnecting() { + if (danaRSService != null) danaRSService.stopConnecting(); + } + + @Override + public void getPumpStatus() { + if (danaRSService != null) + danaRSService.getPumpStatus(); } // DanaR interface @Override - public boolean loadHistory(byte type) { - connectIfNotConnected("loadHistory"); - danaRSService.loadHistory(type); - disconnect("LoadHistory"); - return true; - } + public PumpEnactResult loadHistory(byte type) { + return danaRSService.loadHistory(type); + } // Constraints interface @@ -444,16 +429,6 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, return pump.lastConnection; } - @Override - public synchronized void refreshDataFromPump(String reason) { - log.debug("Refreshing data from pump"); - if (!isConnected() && !isConnecting()) { - connect(reason); - disconnect("RefreshDataFromPump"); - } else - log.debug("Already connecting ..."); - } - @Override public double getBaseBasalRate() { return pump.currentBasal; @@ -491,7 +466,6 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, Treatment t = new Treatment(); boolean connectionOK = false; - connectIfNotConnected("bolus"); if (detailedBolusInfo.insulin > 0 || carbs > 0) connectionOK = danaRSService.bolus(detailedBolusInfo.insulin, (int) carbs, System.currentTimeMillis() + carbTime * 60 * 1000 + 1000, t); // +1000 to make the record different PumpEnactResult result = new PumpEnactResult(); @@ -501,7 +475,6 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); if (Config.logPumpActions) log.debug("deliverTreatment: OK. Asked: " + detailedBolusInfo.insulin + " Delivered: " + result.bolusDelivered); - disconnect("DeliverTreatment"); return result; } else { PumpEnactResult result = new PumpEnactResult(); @@ -527,9 +500,11 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, @Override public synchronized PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, boolean enforceNew) { // Recheck pump status if older than 30 min - if (pump.lastConnection.getTime() + 30 * 60 * 1000L < System.currentTimeMillis()) { - connect("setTempBasalAbsolute old data"); - } + + //This should not be needed while using queue because connection should be done before calling this + //if (pump.lastConnection.getTime() + 30 * 60 * 1000L < System.currentTimeMillis()) { + // connect("setTempBasalAbsolute old data"); + //} PumpEnactResult result = new PumpEnactResult(); @@ -631,7 +606,6 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, return result; } int durationInHours = Math.max(durationInMinutes / 60, 1); - connectIfNotConnected("tempbasal"); boolean connectionOK = danaRSService.tempBasal(percent, durationInHours); if (connectionOK && pump.isTempBasalInProgress && pump.tempBasalPercent == percent) { result.enacted = true; @@ -644,7 +618,6 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, result.isPercent = true; if (Config.logPumpActions) log.debug("setTempBasalPercent: OK"); - disconnect("setTempBasalPercent"); return result; } result.enacted = false; @@ -656,7 +629,6 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, public synchronized PumpEnactResult setHighTempBasalPercent(Integer percent) { PumpEnactResult result = new PumpEnactResult(); - connectIfNotConnected("hightempbasal"); boolean connectionOK = danaRSService.highTempBasal(percent); if (connectionOK && pump.isTempBasalInProgress && pump.tempBasalPercent == percent) { result.enacted = true; @@ -668,7 +640,6 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, result.isPercent = true; if (Config.logPumpActions) log.debug("setHighTempBasalPercent: OK"); - disconnect("setHighTempBasalPercent"); return result; } result.enacted = false; @@ -699,7 +670,6 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, log.debug("setExtendedBolus: Correct extended bolus already set. Current: " + pump.extendedBolusAmount + " Asked: " + insulin); return result; } - connectIfNotConnected("extendedBolus"); boolean connectionOK = danaRSService.extendedBolus(insulin, durationInHalfHours); if (connectionOK && pump.isExtendedInProgress && Math.abs(pump.extendedBolusAmount - insulin) < getPumpDescription().extendedBolusStep) { result.enacted = true; @@ -712,7 +682,6 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, result.isPercent = false; if (Config.logPumpActions) log.debug("setExtendedBolus: OK"); - disconnect("setExtendedBolus"); return result; } result.enacted = false; @@ -727,11 +696,9 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, PumpEnactResult result = new PumpEnactResult(); TemporaryBasal runningTB = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()); if (runningTB != null) { - connectIfNotConnected("tempBasalStop"); danaRSService.tempBasalStop(); result.enacted = true; result.isTempCancel = true; - disconnect("cancelTempBasal"); } if (!pump.isTempBasalInProgress) { result.success = true; @@ -754,11 +721,9 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, PumpEnactResult result = new PumpEnactResult(); ExtendedBolus runningEB = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); if (runningEB != null) { - connectIfNotConnected("extendedBolusStop"); danaRSService.extendedBolusStop(); result.enacted = true; result.isTempCancel = true; - disconnect("extendedBolusStop"); } if (!pump.isExtendedInProgress) { result.success = true; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_APS_History_Events.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_APS_History_Events.java index 8e58dc2cb5..c11b75764c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_APS_History_Events.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_APS_History_Events.java @@ -30,8 +30,8 @@ public class DanaRS_Packet_APS_History_Events extends DanaRS_Packet { private int min = 0; private int sec = 0; - public boolean done; - private int totalCount; + public static boolean done; + private static int totalCount; public static long lastEventTimeLoaded = 0; @@ -77,6 +77,7 @@ public class DanaRS_Packet_APS_History_Events extends DanaRS_Packet { // Last record if (recordCode == (byte) 0xFF) { done = true; + log.debug("Last record received"); return; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/BLEComm.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/BLEComm.java index c2b11dbfb1..f8d18a4d22 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/BLEComm.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/BLEComm.java @@ -23,7 +23,6 @@ import java.util.UUID; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; @@ -59,8 +58,6 @@ public class BLEComm { return instance; } - private Object mConfirmConnect = null; - private final ScheduledExecutorService worker = Executors.newSingleThreadScheduledExecutor(); private ScheduledFuture scheduledDisconnection = null; @@ -116,7 +113,6 @@ public class BLEComm { } public boolean connect(String from, String address, Object confirmConnect) { - mConfirmConnect = confirmConnect; BluetoothManager tBluetoothManager = ((BluetoothManager) MainApp.instance().getApplicationContext().getSystemService(Context.BLUETOOTH_SERVICE)); if (tBluetoothManager == null) { return false; @@ -146,9 +142,9 @@ public class BLEComm { return false; } + log.debug("Trying to create a new connection."); mBluetoothGatt = device.connectGatt(service.getApplicationContext(), false, mGattCallback); setCharacteristicNotification(getUARTReadBTGattChar(), true); - log.debug("Trying to create a new connection."); mBluetoothDevice = device; mBluetoothDeviceAddress = address; mBluetoothDeviceName = device.getName(); @@ -207,6 +203,7 @@ public class BLEComm { } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { close(); isConnected = false; + isConnecting = false; MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTED)); log.debug("Device was disconnected " + gatt.getDevice().getName());//Device was disconnected } @@ -214,9 +211,6 @@ public class BLEComm { public void onServicesDiscovered(BluetoothGatt gatt, int status) { log.debug("onServicesDiscovered"); - - isConnecting = false; - if (status == BluetoothGatt.GATT_SUCCESS) { findCharacteristic(); } @@ -486,18 +480,11 @@ public class BLEComm { pass = pass ^ 3463; DanaRPump.getInstance().rs_password = Integer.toHexString(pass); log.debug("Pump user password: " + Integer.toHexString(pass)); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.CONNECTED)); + MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.CONNECTED)); isConnected = true; isConnecting = false; - service.getPumpStatus(); - scheduleDisconnection(); - if (mConfirmConnect != null) { - synchronized (mConfirmConnect) { - mConfirmConnect.notify(); - mConfirmConnect = null; - } - } + log.debug("RS connected and status read"); break; } break; @@ -527,7 +514,6 @@ public class BLEComm { } else { log.error("Unknown message received " + DanaRS_Packet.toHexString(inputBuffer)); } - scheduleDisconnection(); break; } } catch (Exception e) { @@ -622,7 +608,6 @@ public class BLEComm { if (!message.isReceived()) { log.warn("Reply not received " + message.getFriendlyName()); } - scheduleDisconnection(); } private void SendPairingRequest() { @@ -651,22 +636,4 @@ public class BLEComm { writeCharacteristic_NO_RESPONSE(getUARTWriteBTGattChar(), bytes); } - public void scheduleDisconnection() { - - class DisconnectRunnable implements Runnable { - public void run() { - disconnect("scheduleDisconnection"); - scheduledDisconnection = null; - } - } - // prepare task for execution in 30 sec - // cancel waiting task to prevent sending multiple disconnections - if (scheduledDisconnection != null) - scheduledDisconnection.cancel(false); - Runnable task = new DisconnectRunnable(); - final int sec = 30; - scheduledDisconnection = worker.schedule(task, sec, TimeUnit.SECONDS); - log.debug("Disconnection scheduled"); - } - } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/DanaRSService.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/DanaRSService.java index f7c9b00efa..5e019f320f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/DanaRSService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/DanaRSService.java @@ -20,6 +20,7 @@ import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.db.Treatment; import info.nightscout.androidaps.events.EventAppExit; import info.nightscout.androidaps.events.EventInitializationChanged; @@ -122,7 +123,7 @@ public class DanaRSService extends Service { bleComm.sendMessage(message); } - protected boolean getPumpStatus() { + public void getPumpStatus() { try { MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.gettingpumpstatus))); @@ -172,10 +173,10 @@ public class DanaRSService extends Service { } catch (Exception e) { log.error("Unhandled exception", e); } - return true; + log.debug("Pump status loaded"); } - public boolean loadEvents() { + public void loadEvents() { DanaRS_Packet_APS_History_Events msg; if (lastHistoryFetched == 0) { msg = new DanaRS_Packet_APS_History_Events(0); @@ -189,7 +190,7 @@ public class DanaRSService extends Service { SystemClock.sleep(100); } lastHistoryFetched = DanaRS_Packet_APS_History_Events.lastEventTimeLoaded; - return true; + log.debug("Events loaded"); } @@ -358,8 +359,9 @@ public class DanaRSService extends Service { return true; } - public boolean loadHistory(byte type) { - if (!isConnected()) return false; + public PumpEnactResult loadHistory(byte type) { + PumpEnactResult result = new PumpEnactResult(); + if (!isConnected()) return result; DanaRS_Packet_History_ msg = null; switch (type) { case RecordTypes.RECORD_TYPE_ALARM: @@ -400,7 +402,9 @@ public class DanaRSService extends Service { SystemClock.sleep(200); bleComm.sendMessage(new DanaRS_Packet_General_Set_History_Upload_Mode(0)); } - return true; + result.success = true; + result.comment = "OK"; + return result; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/DanaRv2Plugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/DanaRv2Plugin.java index 8aed0780d5..5304a11a09 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/DanaRv2Plugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/DanaRv2Plugin.java @@ -289,13 +289,6 @@ public class DanaRv2Plugin implements PluginBase, PumpInterface, DanaRInterface, return pump.lastConnection; } - @Override - public void refreshDataFromPump(String reason) { - if (!isConnected() && !isConnecting()) { - connect(reason); - } - } - @Override public double getBaseBasalRate() { return pump.currentBasal; @@ -629,6 +622,16 @@ public class DanaRv2Plugin implements PluginBase, PumpInterface, DanaRInterface, if (sExecutionService != null) sExecutionService.disconnect(from); } + @Override + public void stopConnecting() { + // TODO AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + } + + @Override + public void getPumpStatus() { + // TODO AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + } + @Override public JSONObject getJSONStatus() { if (pump.lastConnection.getTime() + 5 * 60 * 1000L < System.currentTimeMillis()) { @@ -692,7 +695,7 @@ public class DanaRv2Plugin implements PluginBase, PumpInterface, DanaRInterface, */ @Override - public boolean loadHistory(byte type) { + public PumpEnactResult loadHistory(byte type) { return sExecutionService.loadHistory(type); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/services/DanaRv2ExecutionService.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/services/DanaRv2ExecutionService.java index 6eabb668fb..5bd9e4bdbe 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/services/DanaRv2ExecutionService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/services/DanaRv2ExecutionService.java @@ -27,6 +27,7 @@ import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.db.Treatment; import info.nightscout.androidaps.events.EventAppExit; import info.nightscout.androidaps.events.EventInitializationChanged; @@ -506,9 +507,10 @@ public class DanaRv2ExecutionService extends Service { return true; } - public boolean loadHistory(byte type) { + public PumpEnactResult loadHistory(byte type) { + PumpEnactResult result = new PumpEnactResult(); connect("loadHistory"); - if (!isConnected()) return false; + if (!isConnected()) return result; MessageBase msg = null; switch (type) { case RecordTypes.RECORD_TYPE_ALARM: @@ -548,7 +550,9 @@ public class DanaRv2ExecutionService extends Service { } waitMsec(200); mSerialIOThread.sendMessage(new MsgPCCommStop()); - return true; + result.success = true; + result.comment = "OK"; + return result; } public boolean loadEvents() { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpMDI/MDIPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpMDI/MDIPlugin.java index f48f944886..fd5d2bd270 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpMDI/MDIPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpMDI/MDIPlugin.java @@ -147,6 +147,14 @@ public class MDIPlugin implements PluginBase, PumpInterface { public void disconnect(String reason) { } + @Override + public void stopConnecting() { + } + + @Override + public void getPumpStatus() { + } + @Override public PumpEnactResult setNewBasalProfile(Profile profile) { // Do nothing here. we are using MainApp.getConfigBuilder().getActiveProfile().getProfile(); @@ -165,11 +173,6 @@ public class MDIPlugin implements PluginBase, PumpInterface { return new Date(); } - @Override - public void refreshDataFromPump(String reason) { - // do nothing - } - @Override public double getBaseBasalRate() { return 0d; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpPlugin.java index 06b979c52b..4853847e5b 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpPlugin.java @@ -196,12 +196,23 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface { @Override public void connect(String reason) { + if (!BuildConfig.NSCLIENTOLNY) + NSUpload.uploadDeviceStatus(); + lastDataTime = new Date(); } @Override public void disconnect(String reason) { } + @Override + public void stopConnecting() { + } + + @Override + public void getPumpStatus() { + } + @Override public PumpEnactResult setNewBasalProfile(Profile profile) { lastDataTime = new Date(); @@ -221,13 +232,6 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface { return lastDataTime; } - @Override - public void refreshDataFromPump(String reason) { - if (!BuildConfig.NSCLIENTOLNY) - NSUpload.uploadDeviceStatus(); - lastDataTime = new Date(); - } - @Override public double getBaseBasalRate() { Profile profile = MainApp.getConfigBuilder().getProfile(); diff --git a/app/src/main/java/info/nightscout/androidaps/queue/Command.java b/app/src/main/java/info/nightscout/androidaps/queue/Command.java deleted file mode 100644 index 8971b1abf7..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/queue/Command.java +++ /dev/null @@ -1,21 +0,0 @@ -package info.nightscout.androidaps.queue; - -/** - * Created by mike on 09.11.2017. - */ -public abstract class Command { - enum CommandType { - BOLUS, - TEMPBASAL, - EXTENDEDBOLUS, - BASALPROFILE, - READSTATUS - } - - CommandType commandType; - Callback callback; - - public abstract void execute(); - - public abstract String status(); -} diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java index 35b823f38d..6545e91406 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java @@ -13,9 +13,48 @@ 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.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; +import info.nightscout.androidaps.queue.commands.CommandLoadHistory; +import info.nightscout.androidaps.queue.commands.CommandReadStatus; +import info.nightscout.androidaps.queue.commands.CommandSetProfile; +import info.nightscout.androidaps.queue.commands.CommandTempBasalAbsolute; +import info.nightscout.androidaps.queue.commands.CommandTempBasalPercent; /** * Created by mike on 08.11.2017. + * + * DATA FLOW: + * --------- + * + * (request) - > ConfigBuilder.getCommandQueue().bolus(...) + * + * app no longer waits for result but passes Callback + * + * 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 + * + * 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 + * + * 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 + * + * while isConnecting() == true GUI is updated by posting connection progress + * + * if connect is successful: isConnected() becomes true, isConnecting() becomes false + * CommandQueue starts calling execute() of commands. execute() is expected to be blocking (return after finish). + * callback with result is called after finish automatically + * if connect failed: isConnected() becomes false, isConnecting() becomes false + * connect() is called again + * + * when queue is empty, disconnect is called + * */ public class CommandQueue { @@ -24,7 +63,7 @@ public class CommandQueue { private LinkedList queue = new LinkedList<>(); private Command performing; - private QueueThread thread = new QueueThread(this); + private QueueThread thread = null; private PumpEnactResult executingNowError() { PumpEnactResult result = new PumpEnactResult(); @@ -34,26 +73,8 @@ public class CommandQueue { return result; } - public boolean isRunningTempBasal() { - if (performing != null && performing.commandType == Command.CommandType.TEMPBASAL) - return true; - return false; - } - - public boolean isRunningBolus() { - if (performing != null && performing.commandType == Command.CommandType.BOLUS) - return true; - return false; - } - - public boolean isRunningExtendedBolus() { - if (performing != null && performing.commandType == Command.CommandType.EXTENDEDBOLUS) - return true; - return false; - } - - public boolean isRunningProfile() { - if (performing != null && performing.commandType == Command.CommandType.BASALPROFILE) + public boolean isRunning(Command.CommandType type) { + if (performing != null && performing.commandType == type) return true; return false; } @@ -67,14 +88,22 @@ public class CommandQueue { } private synchronized void add(Command command) { + // inject reading of status when adding first command to the queue + if (queue.size() == 0 && command.commandType != Command.CommandType.READSTATUS) + queue.add(new CommandReadStatus("Queue", null)); queue.add(command); } - protected synchronized void pickup() { + synchronized void pickup() { performing = queue.poll(); } - public void clear() { + synchronized void clear() { + performing = null; + for (int i = 0; i < queue.size(); i++) { + queue.get(i).cancel(); + } + queue.clear(); } @@ -90,15 +119,20 @@ public class CommandQueue { performing = null; } + // After new command added to the queue + // start thread again if not already running private void notifyAboutNewCommand() { - if (!thread.isAlive()) + if (thread == null || thread.getState() == Thread.State.TERMINATED) { + thread = new QueueThread(this); thread.start(); + } } // returns true if command is queued public boolean bolus(DetailedBolusInfo detailedBolusInfo, Callback callback) { - if (isRunningBolus()) { - callback.result(executingNowError()).run(); + if (isRunning(Command.CommandType.BOLUS)) { + if (callback != null) + callback.result(executingNowError()).run(); return false; } @@ -115,12 +149,13 @@ public class CommandQueue { // returns true if command is queued public boolean tempBasalAbsolute(double absoluteRate, int durationInMinutes, boolean enforceNew, Callback callback) { - if (isRunningTempBasal()) { - callback.result(executingNowError()).run(); + if (isRunning(Command.CommandType.TEMPBASAL)) { + if (callback != null) + callback.result(executingNowError()).run(); return false; } - // remove all unfinished boluese + // remove all unfinished removeAll(Command.CommandType.TEMPBASAL); // add new command to queue @@ -133,12 +168,13 @@ public class CommandQueue { // returns true if command is queued public boolean tempBasalPercent(int percent, int durationInMinutes, Callback callback) { - if (isRunningTempBasal()) { - callback.result(executingNowError()).run(); + if (isRunning(Command.CommandType.TEMPBASAL)) { + if (callback != null) + callback.result(executingNowError()).run(); return false; } - // remove all unfinished boluese + // remove all unfinished removeAll(Command.CommandType.TEMPBASAL); // add new command to queue @@ -151,12 +187,13 @@ public class CommandQueue { // returns true if command is queued public boolean extendedBolus(double insulin, int durationInMinutes, Callback callback) { - if (isRunningExtendedBolus()) { - callback.result(executingNowError()).run(); + if (isRunning(Command.CommandType.EXTENDEDBOLUS)) { + if (callback != null) + callback.result(executingNowError()).run(); return false; } - // remove all unfinished boluese + // remove all unfinished removeAll(Command.CommandType.EXTENDEDBOLUS); // add new command to queue @@ -169,12 +206,13 @@ public class CommandQueue { // returns true if command is queued public boolean cancelTempBasal(boolean enforceNew, Callback callback) { - if (isRunningTempBasal()) { - callback.result(executingNowError()).run(); + if (isRunning(Command.CommandType.TEMPBASAL)) { + if (callback != null) + callback.result(executingNowError()).run(); return false; } - // remove all unfinished boluese + // remove all unfinished removeAll(Command.CommandType.TEMPBASAL); // add new command to queue @@ -187,12 +225,13 @@ public class CommandQueue { // returns true if command is queued public boolean cancelExtended(Callback callback) { - if (isRunningExtendedBolus()) { - callback.result(executingNowError()).run(); + if (isRunning(Command.CommandType.EXTENDEDBOLUS)) { + if (callback != null) + callback.result(executingNowError()).run(); return false; } - // remove all unfinished boluese + // remove all unfinished removeAll(Command.CommandType.EXTENDEDBOLUS); // add new command to queue @@ -205,12 +244,13 @@ public class CommandQueue { // returns true if command is queued public boolean setProfile(Profile profile, Callback callback) { - if (isRunningProfile()) { - callback.result(executingNowError()).run(); + if (isRunning(Command.CommandType.BASALPROFILE)) { + if (callback != null) + callback.result(executingNowError()).run(); return false; } - // remove all unfinished boluese + // remove all unfinished removeAll(Command.CommandType.BASALPROFILE); // add new command to queue @@ -221,13 +261,53 @@ public class CommandQueue { return true; } - Spanned spannedStatus() { + // returns true if command is queued + public boolean readStatus(String reason, Callback callback) { + if (isRunning(Command.CommandType.READSTATUS)) { + if (callback != null) + callback.result(executingNowError()).run(); + return false; + } + + // remove all unfinished + removeAll(Command.CommandType.READSTATUS); + + // 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; + } + + public Spanned spannedStatus() { String s = ""; if (performing != null) { - s += "" + performing.status() + "
"; + s += "" + performing.status() + ""; } for (int i = 0; i < queue.size(); i++) { - s += queue.get(i).status() + "
"; + if (i != 0) + s += "
"; + s += queue.get(i).status(); } return Html.fromHtml(s); } diff --git a/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.java b/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.java index 41c9f42434..c4892414a9 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.java @@ -5,10 +5,13 @@ import android.os.SystemClock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; import info.nightscout.androidaps.events.EventPumpStatusChanged; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.queue.events.EventQueueChanged; /** * Created by mike on 09.11.2017. @@ -18,8 +21,6 @@ public class QueueThread extends Thread { private static Logger log = LoggerFactory.getLogger(QueueThread.class); CommandQueue queue; - boolean keepRunning = false; - private long connectionStartTime = 0; @@ -27,40 +28,60 @@ public class QueueThread extends Thread { super(QueueThread.class.toString()); this.queue = queue; - keepRunning = true; } @Override public final void run() { + MainApp.bus().post(new EventQueueChanged()); + connectionStartTime = System.currentTimeMillis(); PumpInterface pump = ConfigBuilderPlugin.getActivePump(); - while (keepRunning) { + while (true) { + log.debug("Looping ..."); + long secondsElapsed = (System.currentTimeMillis() - connectionStartTime) / 1000; if (pump.isConnecting()) { - long secondsElapsed = (System.currentTimeMillis() - connectionStartTime) / 1000; + log.debug("State: connecting"); MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.CONNECTING, (int) secondsElapsed)); SystemClock.sleep(1000); + continue; + } + + if (!pump.isConnected() && secondsElapsed > Constants.PUMP_MAX_CONNECTION_TIME_IN_SECONDS) { + log.debug("State: timed out"); + MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.connectiontimedout))); + pump.stopConnecting(); + queue.clear(); + return; } if (!pump.isConnected()) { - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.CONNECTING)); - pump.connect("Not connected"); - connectionStartTime = System.currentTimeMillis(); + log.debug("State: connect"); + MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.CONNECTING, (int) secondsElapsed)); + pump.connect("Connection needed"); SystemClock.sleep(1000); + continue; } if (queue.performing() == null) { // Pickup 1st command and set performing variable if (queue.size() > 0) { + log.debug("State: performing"); queue.pickup(); + MainApp.bus().post(new EventQueueChanged()); queue.performing().execute(); queue.resetPerforming(); + MainApp.bus().post(new EventQueueChanged()); + SystemClock.sleep(100); + continue; } - } if (queue.size() == 0 && queue.performing() == null) { + log.debug("State: queue empty. disconnect"); + MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); pump.disconnect("Queue empty"); - keepRunning = false; + MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTED)); + return; } } } diff --git a/app/src/main/java/info/nightscout/androidaps/queue/commands/Command.java b/app/src/main/java/info/nightscout/androidaps/queue/commands/Command.java new file mode 100644 index 0000000000..763de9e840 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/Command.java @@ -0,0 +1,35 @@ +package info.nightscout.androidaps.queue.commands; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.queue.Callback; + +/** + * Created by mike on 09.11.2017. + */ +public abstract class Command { + public enum CommandType { + BOLUS, + TEMPBASAL, + EXTENDEDBOLUS, + BASALPROFILE, + READSTATUS, + LOADHISTORY // so far only Dana specific + } + + public CommandType commandType; + protected Callback callback; + + public abstract void execute(); + + public abstract String status(); + + public void cancel() { + PumpEnactResult result = new PumpEnactResult(); + result.success = false; + result.comment = MainApp.sResources.getString(R.string.connectiontimedout); + if (callback != null) + callback.result(result).run(); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandBolus.java b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandBolus.java similarity index 67% rename from app/src/main/java/info/nightscout/androidaps/queue/CommandBolus.java rename to app/src/main/java/info/nightscout/androidaps/queue/commands/CommandBolus.java index ef9472c14e..cd158128f0 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/CommandBolus.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandBolus.java @@ -1,8 +1,9 @@ -package info.nightscout.androidaps.queue; +package info.nightscout.androidaps.queue.commands; +import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.data.DetailedBolusInfo; import info.nightscout.androidaps.data.PumpEnactResult; -import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.queue.Callback; import info.nightscout.utils.DecimalFormatter; /** @@ -12,7 +13,7 @@ import info.nightscout.utils.DecimalFormatter; public class CommandBolus extends Command { DetailedBolusInfo detailedBolusInfo; - CommandBolus(DetailedBolusInfo detailedBolusInfo, Callback callback) { + public CommandBolus(DetailedBolusInfo detailedBolusInfo, Callback callback) { commandType = CommandType.BOLUS; this.detailedBolusInfo = detailedBolusInfo; this.callback = callback; @@ -20,7 +21,7 @@ public class CommandBolus extends Command { @Override public void execute() { - PumpEnactResult r = ConfigBuilderPlugin.getActivePump().deliverTreatment(detailedBolusInfo); + PumpEnactResult r = MainApp.getConfigBuilder().deliverTreatment(detailedBolusInfo); if (callback != null) callback.result(r).run(); } diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandCancelExtendedBolus.java b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandCancelExtendedBolus.java similarity index 69% rename from app/src/main/java/info/nightscout/androidaps/queue/CommandCancelExtendedBolus.java rename to app/src/main/java/info/nightscout/androidaps/queue/commands/CommandCancelExtendedBolus.java index 4c50ab04b3..f6a6cf1bb4 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/CommandCancelExtendedBolus.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandCancelExtendedBolus.java @@ -1,7 +1,8 @@ -package info.nightscout.androidaps.queue; +package info.nightscout.androidaps.queue.commands; +import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.data.PumpEnactResult; -import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.queue.Callback; /** * Created by mike on 09.11.2017. @@ -16,7 +17,7 @@ public class CommandCancelExtendedBolus extends Command { @Override public void execute() { - PumpEnactResult r = ConfigBuilderPlugin.getActivePump().cancelExtendedBolus(); + PumpEnactResult r = MainApp.getConfigBuilder().cancelExtendedBolus(); if (callback != null) callback.result(r).run(); } diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandCancelTempBasal.java b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandCancelTempBasal.java similarity index 62% rename from app/src/main/java/info/nightscout/androidaps/queue/CommandCancelTempBasal.java rename to app/src/main/java/info/nightscout/androidaps/queue/commands/CommandCancelTempBasal.java index dce8f53784..3c401b4136 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/CommandCancelTempBasal.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandCancelTempBasal.java @@ -1,7 +1,8 @@ -package info.nightscout.androidaps.queue; +package info.nightscout.androidaps.queue.commands; +import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.data.PumpEnactResult; -import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.queue.Callback; /** * Created by mike on 09.11.2017. @@ -10,7 +11,7 @@ import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; public class CommandCancelTempBasal extends Command { boolean enforceNew; - CommandCancelTempBasal(boolean enforceNew, Callback callback) { + public CommandCancelTempBasal(boolean enforceNew, Callback callback) { commandType = CommandType.TEMPBASAL; this.enforceNew = enforceNew; this.callback = callback; @@ -18,7 +19,7 @@ public class CommandCancelTempBasal extends Command { @Override public void execute() { - PumpEnactResult r = ConfigBuilderPlugin.getActivePump().cancelTempBasal(enforceNew); + PumpEnactResult r = MainApp.getConfigBuilder().cancelTempBasal(enforceNew); if (callback != null) callback.result(r).run(); } diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandExtendedBolus.java b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandExtendedBolus.java similarity index 68% rename from app/src/main/java/info/nightscout/androidaps/queue/CommandExtendedBolus.java rename to app/src/main/java/info/nightscout/androidaps/queue/commands/CommandExtendedBolus.java index 85b3c70315..27f64c0ef3 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/CommandExtendedBolus.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandExtendedBolus.java @@ -1,15 +1,16 @@ -package info.nightscout.androidaps.queue; +package info.nightscout.androidaps.queue.commands; +import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.data.PumpEnactResult; -import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.queue.Callback; /** * Created by mike on 09.11.2017. */ public class CommandExtendedBolus extends Command { - double insulin; - int durationInMinutes; + private double insulin; + private int durationInMinutes; public CommandExtendedBolus(double insulin, int durationInMinutes, Callback callback) { commandType = CommandType.EXTENDEDBOLUS; @@ -20,7 +21,7 @@ public class CommandExtendedBolus extends Command { @Override public void execute() { - PumpEnactResult r = ConfigBuilderPlugin.getActivePump().setExtendedBolus(insulin, durationInMinutes); + PumpEnactResult r = MainApp.getConfigBuilder().setExtendedBolus(insulin, durationInMinutes); if (callback != null) callback.result(r).run(); } diff --git a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandLoadHistory.java b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandLoadHistory.java new file mode 100644 index 0000000000..16e9a18048 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandLoadHistory.java @@ -0,0 +1,38 @@ +package info.nightscout.androidaps.queue.commands; + +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.interfaces.DanaRInterface; +import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.queue.Callback; +import info.nightscout.androidaps.queue.commands.Command; + +/** + * Created by mike on 10.11.2017. + */ + +public class CommandLoadHistory extends Command { + byte type; + + public CommandLoadHistory(byte type, Callback callback) { + commandType = CommandType.LOADHISTORY; + this.type = type; + this.callback = callback; + } + + @Override + public void execute() { + PumpInterface pump = ConfigBuilderPlugin.getActivePump(); + if (pump instanceof DanaRInterface) { + DanaRInterface danaPump = (DanaRInterface) pump; + PumpEnactResult r = danaPump.loadHistory(type); + if (callback != null) + callback.result(r).run(); + } + } + + @Override + public String status() { + return "LOADHISTORY " + type; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandReadStatus.java b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandReadStatus.java similarity index 50% rename from app/src/main/java/info/nightscout/androidaps/queue/CommandReadStatus.java rename to app/src/main/java/info/nightscout/androidaps/queue/commands/CommandReadStatus.java index e8ceb84091..642c56e2dd 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/CommandReadStatus.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandReadStatus.java @@ -1,25 +1,30 @@ -package info.nightscout.androidaps.queue; +package info.nightscout.androidaps.queue.commands; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.queue.Callback; /** * Created by mike on 09.11.2017. */ public class CommandReadStatus extends Command { + String reason; - CommandReadStatus(Callback callback) { + public CommandReadStatus(String reason, Callback callback) { commandType = CommandType.READSTATUS; + this.reason = reason; this.callback = callback; } @Override public void execute() { - // do nothing by default. Status is read on connection + MainApp.getConfigBuilder().getPumpStatus(); if (callback != null) callback.result(null).run(); } @Override public String status() { - return "READ STATUS"; + return "READSTATUS " + reason; } } diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandSetProfile.java b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandSetProfile.java similarity index 64% rename from app/src/main/java/info/nightscout/androidaps/queue/CommandSetProfile.java rename to app/src/main/java/info/nightscout/androidaps/queue/commands/CommandSetProfile.java index 273e3b344c..b1c8e452ae 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/CommandSetProfile.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandSetProfile.java @@ -1,8 +1,9 @@ -package info.nightscout.androidaps.queue; +package info.nightscout.androidaps.queue.commands; +import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; -import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.queue.Callback; /** * Created by mike on 09.11.2017. @@ -11,7 +12,7 @@ import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; public class CommandSetProfile extends Command { Profile profile; - CommandSetProfile(Profile profile, Callback callback) { + public CommandSetProfile(Profile profile, Callback callback) { commandType = CommandType.BASALPROFILE; this.profile = profile; this.callback = callback; @@ -19,7 +20,7 @@ public class CommandSetProfile extends Command { @Override public void execute() { - PumpEnactResult r = ConfigBuilderPlugin.getActivePump().setNewBasalProfile(profile); + PumpEnactResult r = MainApp.getConfigBuilder().setNewBasalProfile(profile); if (callback != null) callback.result(r).run(); } diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandTempBasalAbsolute.java b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandTempBasalAbsolute.java similarity index 64% rename from app/src/main/java/info/nightscout/androidaps/queue/CommandTempBasalAbsolute.java rename to app/src/main/java/info/nightscout/androidaps/queue/commands/CommandTempBasalAbsolute.java index 136efe72be..ffd32858ef 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/CommandTempBasalAbsolute.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandTempBasalAbsolute.java @@ -1,7 +1,8 @@ -package info.nightscout.androidaps.queue; +package info.nightscout.androidaps.queue.commands; +import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.data.PumpEnactResult; -import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.queue.Callback; /** * Created by mike on 09.11.2017. @@ -12,7 +13,7 @@ public class CommandTempBasalAbsolute extends Command { double absoluteRate; boolean enforceNew; - CommandTempBasalAbsolute(double absoluteRate, int durationInMinutes, boolean enforceNew, Callback callback) { + public CommandTempBasalAbsolute(double absoluteRate, int durationInMinutes, boolean enforceNew, Callback callback) { commandType = CommandType.TEMPBASAL; this.absoluteRate = absoluteRate; this.durationInMinutes = durationInMinutes; @@ -22,7 +23,7 @@ public class CommandTempBasalAbsolute extends Command { @Override public void execute() { - PumpEnactResult r = ConfigBuilderPlugin.getActivePump().setTempBasalAbsolute(absoluteRate, durationInMinutes, enforceNew); + PumpEnactResult r = MainApp.getConfigBuilder().setTempBasalAbsolute(absoluteRate, durationInMinutes, enforceNew); if (callback != null) callback.result(r).run(); } diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandTempBasalPercent.java b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandTempBasalPercent.java similarity index 64% rename from app/src/main/java/info/nightscout/androidaps/queue/CommandTempBasalPercent.java rename to app/src/main/java/info/nightscout/androidaps/queue/commands/CommandTempBasalPercent.java index a645f7a93a..88846eebd4 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/CommandTempBasalPercent.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandTempBasalPercent.java @@ -1,7 +1,8 @@ -package info.nightscout.androidaps.queue; +package info.nightscout.androidaps.queue.commands; +import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.data.PumpEnactResult; -import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.queue.Callback; /** * Created by mike on 09.11.2017. @@ -11,7 +12,7 @@ public class CommandTempBasalPercent extends Command { int durationInMinutes; int percent; - CommandTempBasalPercent(int percent, int durationInMinutes, Callback callback) { + public CommandTempBasalPercent(int percent, int durationInMinutes, Callback callback) { commandType = CommandType.TEMPBASAL; this.percent = percent; this.durationInMinutes = durationInMinutes; @@ -20,7 +21,7 @@ public class CommandTempBasalPercent extends Command { @Override public void execute() { - PumpEnactResult r = ConfigBuilderPlugin.getActivePump().setTempBasalPercent(percent, durationInMinutes); + PumpEnactResult r = MainApp.getConfigBuilder().setTempBasalPercent(percent, durationInMinutes); if (callback != null) callback.result(r).run(); } diff --git a/app/src/main/java/info/nightscout/androidaps/queue/events/EventQueueChanged.java b/app/src/main/java/info/nightscout/androidaps/queue/events/EventQueueChanged.java new file mode 100644 index 0000000000..b0a53afd13 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/queue/events/EventQueueChanged.java @@ -0,0 +1,8 @@ +package info.nightscout.androidaps.queue.events; + +/** + * Created by mike on 11.11.2017. + */ + +public class EventQueueChanged { +} diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.java b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.java index e3c77d0122..a49eb6f30c 100644 --- a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.java +++ b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.java @@ -47,29 +47,11 @@ public class KeepAliveReceiver extends BroadcastReceiver { SharedPreferences SP = PreferenceManager.getDefaultSharedPreferences(MainApp.instance().getApplicationContext()); if (SP.getBoolean("syncprofiletopump", false) && !pump.isThisProfileSet(profile)) { - Thread t = new Thread(new Runnable() { - @Override - public void run() { - pump.setNewBasalProfile(profile); - } - }); - t.start(); + MainApp.getConfigBuilder().getCommandQueue().setProfile(profile, null); } else if (isStatusOutdated && !pump.isBusy()) { - Thread t = new Thread(new Runnable() { - @Override - public void run() { - pump.refreshDataFromPump("KeepAlive. Status outdated."); - } - }); - t.start(); + MainApp.getConfigBuilder().getCommandQueue().readStatus("KeepAlive. Status outdated.", null); } else if (isBasalOutdated && !pump.isBusy()) { - Thread t = new Thread(new Runnable() { - @Override - public void run() { - pump.refreshDataFromPump("KeepAlive. Basal outdated."); - } - }); - t.start(); + MainApp.getConfigBuilder().getCommandQueue().readStatus("KeepAlive. Basal outdated.", null); } } diff --git a/app/src/main/res/layout/danar_fragment.xml b/app/src/main/res/layout/danar_fragment.xml index 29c9398a04..ac48032690 100644 --- a/app/src/main/res/layout/danar_fragment.xml +++ b/app/src/main/res/layout/danar_fragment.xml @@ -124,6 +124,13 @@ + + Date: Sat, 11 Nov 2017 16:20:34 +0100 Subject: [PATCH 05/14] add bolus sound --- .../info/nightscout/androidaps/Constants.java | 2 +- .../plugins/Overview/Notification.java | 24 +++++++++++++++--- .../nightscout/androidaps/queue/Callback.java | 11 +------- .../queue/commands/CommandReadStatus.java | 2 +- app/src/main/res/raw/boluserror.mp3 | Bin 0 -> 93251 bytes 5 files changed, 24 insertions(+), 15 deletions(-) create mode 100644 app/src/main/res/raw/boluserror.mp3 diff --git a/app/src/main/java/info/nightscout/androidaps/Constants.java b/app/src/main/java/info/nightscout/androidaps/Constants.java index d643d1a82d..46e8bc33a2 100644 --- a/app/src/main/java/info/nightscout/androidaps/Constants.java +++ b/app/src/main/java/info/nightscout/androidaps/Constants.java @@ -60,5 +60,5 @@ public class Constants { public static final double DEVIATION_TO_BE_EQUAL = 2.0; // Pump - public static final int PUMP_MAX_CONNECTION_TIME_IN_SECONDS = 5 * 60; + public static final int PUMP_MAX_CONNECTION_TIME_IN_SECONDS = 60 - 1; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Notification.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Notification.java index a857153c73..2ad7974b89 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Notification.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Notification.java @@ -61,9 +61,6 @@ public class Notification { public NSAlarm nsAlarm = null; public Integer soundId = null; - public Notification() { - } - public Notification(int id, Date date, String text, int level, Date validTo) { this.id = id; this.date = date; @@ -88,6 +85,27 @@ public class Notification { this.validTo = new Date(0); } + public Notification(int id) { + this.id = id; + this.date = new Date(); + this.validTo = new Date(0); + } + + public Notification text(String text) { + this.text = text; + return this; + } + + public Notification level(int level) { + this.level = level; + return this; + } + + public Notification sound(int soundId) { + this.soundId = soundId; + return this; + } + public Notification(NSAlarm nsAlarm) { this.date = new Date(); this.validTo = new Date(0); diff --git a/app/src/main/java/info/nightscout/androidaps/queue/Callback.java b/app/src/main/java/info/nightscout/androidaps/queue/Callback.java index a4b458ed43..8ea5132290 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/Callback.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/Callback.java @@ -5,20 +5,11 @@ import info.nightscout.androidaps.data.PumpEnactResult; /** * Created by mike on 09.11.2017. */ -public class Callback { +public abstract class Callback implements Runnable { public PumpEnactResult result; - Runnable runnable; - - public Callback(Runnable runnable) { - this.runnable = runnable; - } public Callback result(PumpEnactResult result) { this.result = result; return this; } - - public void run() { - runnable.run(); - } } diff --git a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandReadStatus.java b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandReadStatus.java index 642c56e2dd..64da0c0bc7 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandReadStatus.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandReadStatus.java @@ -25,6 +25,6 @@ public class CommandReadStatus extends Command { @Override public String status() { - return "READSTATUS " + reason; + return "READSTATUS"; } } diff --git a/app/src/main/res/raw/boluserror.mp3 b/app/src/main/res/raw/boluserror.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..5a1f3ea1c985f452e8b2496894d03fd546bb05da GIT binary patch literal 93251 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
y+($WnQ0=C%4ke`ID_A{rKN1Ea^S7K zt&GY3)X2_f<{BLL@a954A?bee$4E@_=;~B~$KaF>t=ObYtGd&|1^dFbq%fl&tkzj| zU(Z}B3U^V>F^39o&A2jJ>s5g`Jk4v(%t3S0FRD{1ITU~)ks9_>oTIzQV%vw$B zJgqZFJm--}p4i4rW`3Q#4N9X-Ivt8lgnz|b@ky^Mr#^<0aVJ^e zJQ(`XyhJH4PQUz`4kRBptsJoSHlZ>|$7G;GuE3>UYWUE7CSa%Ft&tPl;wqgYZ)(u% zG(KSbkz;?%VLq{JNfvEnV62sDa-5Mmd{*L0kE_Mf-U^oOhi- z3!8MWW#l{eCbM?r6*k_gdk+*BadH_X06acWuH$4$*a!x7np+{QxV)<_6-91pGP9o? z4o1Uk*k14otF63+zMHToH+{I#UMKYXPD9gB97bbJ&Bl@0=72lDxs(wyxNUYt;}TGT z!_@v`)I0gEpd*Kvk6`3hC6;j%D+h@7TkN{~Uo1+~#qHV6SsD2FZ#BpG%;}7or2ttB zvsh%QF)i}ryN1}P+vXMe9F5g?rVN>NxZjpc%Ii*Qdn2#8(+wSzO{Z zv}l#wcX%p@C2QZXH=cd0PoTl`613RK9d%hZcLbSf8goUzfXp*d(a{v=%jV+b*XS1_9Nr+0p7%O24mM|K4f2=OqQi-+8)43HUd6tEyGw`$QnnrWx~*+8423YksCkR#~i zZ0RUMG1$u0lcuOT^;F@Z^!~tf(Vj$I*u_LUvIVtwh=F1wi!AWayI;l>ba~HJ@pI$w zxGOM(LLZ;$v44flQU}e;HiO~~P8hY1zS>zWT7|UCZV{V;5E(mt^&! zNg^&mx=OE1mjv7$Iq6j|^yr&A4@NMWJMy{)42HIW_{gEwlciVooh|zU%EhG@Eh61l zCWcwW9(YGCn#Wx?LBU+eZAP*kJnlq?E#T#`K}DXjF1IN{sEfA#I^DpZobrp9^`wA0?GrnB4;@$GDZ@!MJ2ASC$o$ zTNFYxQ7x(HDFYHcowAx8G3}IF24#}fo-G+rqx6IP{OB*%INy<8x9Lgt? z#J5PeRad&RJ*8DcED02&ii+9i>Thh~?=5glKZyAL2g=JP%OoX-1>RXpR8TVnmAP){ z>*#1)Rdwy~A1Y{3XAxvDd!7(J(GYRTK0hr0S>B99_n@9p^1THIyG z&5g@-D}lk#af&97jW`4k3nj7?{>7Nfx)797-mdIA+0s{BcVoHdJ^SkwVK)*qwS0X3 zagM4X8ur&r!UbV_oe<`r?UZW5t`6ge!QvdYUA37w_ZSJ^_rR*w2{|7(J4m2J6~`pY z|C%!j!mK2aZUr8Uk2r7IJ(p#Rvlz}{^&2@_e%Z?Mvh}I7KiZFI%JG@c6sZImOi)#5 z7Wovgt zG!lZK^U9W;pjY4*2nZaSikZn88(#(LVZ<&Y7w*l-UDK}<#52^2Y)$X}R96%f~wt;v|wa-1R!lv7h+7_7+POB?6J)AxMS2zbaGt zbd$@vesSXF-pNVY_ZNSEkdC*SQoi0Ax`#_|q~}j!!%V2r8fzzHC#VD|kKgV|p!Azi zX{p1Q=gAerp-^iAx6t6*Mz9rPgeB~K?PGQVGGW>C-yf2d>(!V((7X6=X?82gg0}M4 znpcqoXzek`w}&{tYU}gv9Gs>|Oa4=ME>~=s=SAiWuh`Vh2`_Bf)?F|DUtW#%v8PgK-ISB=-7pWuHvSEQ%;J z?x`jkwgRKX2BMgq(ZZ}G2)&nGGqKDMsxO9l0^jVHac^-R1^Ay`Tt&1|vi|4yq=>Kw z))PvLL~vD+OJcDSym37%k@t{_nwjLbgwzNEm;);*Oaj3=QO$QnKZB3VBeHDU#NH}C z@bHR@r!ggKFDZR38LAg?z3+X~yT`LygNpW`JQ6F@cUP-Ck7^njjdodcc$)uOT12Bp zhIyR-T*+g)ZH4Tu-q$bhuAG?;tqgc{ceB65>Dj%b5d}qLXxQt;D8(ENOrI9cMZwDW z+nc)UDMbmb10!l>gEyaxkzQK^=wDHL8`&8;v1!ye}ip;3SVxR+}r^$D^J z^U>ESz1AA6CRpb=yo6XX;|il~=Uckt2pKJarkZ<~ zczmbW3h4w~yI+I(PR9CgE53?0y5k>|4ohO7Zm;{pG7;vtm*>z5{?K0KfexwC)_eI~ zakh7`oXF^Kihc<-vn&1;2W-iF=4OKJ(6Gxw-Z8^^-mTT3!#YZrZUCknMjT@-u%t3u zFxCI=NV2lABlOmX41UQb=<4BRi2=HzMfY`_i;&%g^B@4@$epLLgWit!1rlb=Ik1U* z?$|}>0!5^QEvH^`B|WANkeBD)_)8@yp#O_>GrWafmCT`1FQwV=&+&J6^QF~Ya$S5^ zW?Fj8SIXn-8&6!gQ>-H|fD&_=yFl8zgvGHPS$z3u8sHKR)8at+Zqx|>gk^MTB=;>U zigMWQZN~!3-k*1YZ~3@U{OtOJw+H^aX*g3#*mOE^@btDMmV`Je#FCzoF3HrTE7K>t z^{}8o6*{}!+bG9!!Pk1=`UI?`k!?R;;5nyn(*xSi&lxBcZh(=_K-T#VDKo$^Z;-8D z(ta;JD7ihA4u*KGjx)b)yKXZLQl$fl5bCG9C1Q#4yAu^x?l7Y20?G@JO$A|7#;u2C zI~bZlHV0A?Bs#A5`?Dq<)7lYd?=pzvQl!mZQaE*j!L2kBDsMainY?ttxKTjRobiVS zNK0RZfD7mO4_(?uDqYUQ9 zqgQy%M6e>8Se3Xrd%*fUk`n=+Kf{B?ir$yG=>C0}LqIH%(Z{uB$F~p%a@?v2bvxc= zM_Rvj?TaeOtGcXdd)MDHe%B265`1B3rjxDx+MhGL1?e#Fm2j~Pl`lin6})kNL%j3$ z&;iCN0D@M7fN_e_sLxz(nqe@+AYhe9aC17u`S#xXWaJGRH+QZ5w;VySCfn%YgU->h zymOSpGvs+Pbu+=of+lZze}_F<;m4e1QS=s^UWI~Ep)N%XX9AlH{eI*q8suaP(&KJ; z7jz;5;v)?QR~P_h=7BI_XFn$|cS1K*T4fzQr?445GB%ubPB=|S^;VmO<9ptd16fbm zbyBUnr0+bF>B|2kHZkMNBiygLyjD5z%@a6-4gqDoBP|?r%S=)!F%oETKr^YncqX0p z;f}T+K)#!@O~El(b(72U{@i}{RMQ{#_37rfU%Lgd9?agp)Oq8+xavUGw{}SQiV+3b zmH+a@xZPWngQ1~Iaa-$W%xR_C@_Htg*oz5Wn90m;Dvz`tNiFcR}L*Wvus}dX@ zF~UA|a;?yc<~lJc9IsHsm^4b~(-2Qww-Hz2;Pi^W0jZ@nj|Rq3Q+@3HZ(sV24UQig z`{)VyF>6m*`t%*pTafw(B!{=9Yt?8AK+w&*_iePR@Dt$*B`Ibhd$GUp11a{;kE@b4 zF&R~)HlWKl6;#s@T|ht27dPi6%v9{1#X;@?m{fCLDH=Zuq_(NRjp0&&XL4@A3KDQYN3+^#_v4MEYNXm(u{U5a zG7(QSnijzxx1_o0Nc^j9eU0SW4T(07wc}i+i0=IIFe2fcXqu=Pv_mLkTq3m;f1Z?v zv2nH6t1VXgboOFsQ)=4Nj0CIUpwtYV;$4+fHI||6YaF8RC^eEI3kdJ(8YcXEZ_jO$ z4J)Bg&YHna^YNC)Y!?!p9DyD2Rr~KPrEO7%R1()5!mOc~_sgLa^NfT%5PEo{1Sm)risz_8+x)KR2*MEvznH9MFTCf`xj5R|PV^W7KPvpP z_1kT-zm(KdFyMA!+^v?9^dSCuo3Q9eHGFzMno{cfvt0;Z4IGi_mCA0YV!u)weD)4& z0kkH2Z)|7m<)vaRxwfDB4->xAU=zvTw|H6ne?&jgH_$heeBu&lDo3uP>%DCMKYWHh z3NZ=LO7iMK1Ho$5OK6x6v=%ki2=SERx`3>+KKwDZBp|1hS|0D_dk_m(1{}8@9M~Y< zMi%Uy%&-z*g7z8CJ)>7rr2Up>{qNt3Q9UKFFstfjIcF^rs%R-k%MvXsdUk8rmCqs}2%3BNdi& zdn{Ks!~Lc37?>(3N z8rkjMzUJ}g@37dgw1%?xh2{!NX>7qM#|4P@|3seyl(JF6Mq3NGxGZRGo1~+<+O-c@ zFLBQsF7jILFDj^95raO75ySVU9maf1VUjlv`BsFVxs?~NU&aPdQ8YFgKND{VHPL0Z zSBb44hlAMASi@yrB@mG_-Pw#cWx+aZQbkoXOK@n8ad;QtI4pN;RH5_NKFpj(l-*Fx zet846FLeBw2UgN=w%8QuY%67S3qDJI|b-vRG@SS?8?wQ{Hxnc?rwf~#CH)(#Ow5~phImOV|v|_(+ zj`1NY5wn#p=VFAT2ENV(KKys1cOgcs!6#o_-2}&cwK~uk`Nmj!9E~ zYQ`$TU{tJMSnen4$L}$!W319kb#-E_gBdAa^EH|@YcuQQm>*aa{X`tYKgMf)ALu^q z=KjvDr2|xYL$x$*07-f<;t2qc@IVP*6Q5W`KaE|w9(WYukI@tW!xZ)1FBO3G$PF+z zf0+lC8gN%#fR)`@b=i|b@U>Hi*eKF%wNGxZHCeeNv|=EWl-no@KGO62U~yaWFdWQ2 zjw!isS*p+?zO9$X5*};T4gK;v&L+x#1Ly*)R-g<|3M+Bnn}zVF&r%vRC@`coSQ;ti=H~ zt9thMH?5gt@eG#@W{%KaKcaeXtui9Y@=^GEqwr@av$~<6>ck;`20cm2k6u3w)CPkF zHa2;-A01YdHp<&CF1q)WK1M=cj6e9L!m(yxAGD#>2qbL2@7{u6s0=@X zehrF#4UX3_Hr4;G^=)Zgl~a|K(?fSe82z`EPcnVOgIPCIeb;(6M&xC9e`UVjD*bnp z+OP=x2pfzj_04BnYhkGBU10q(bSQDc!U3In(h8;!=l)B9P8$lKU0IQij>VyK(3M2o zllBD5?s4SOHx@+_Dp8Oy8)!|R5qD_~K*$ykI)21)p(gJ6sw&A9i~aY1N}K2j=2fskyDT)kC|WL{HJ`S@q#PVjC1)0$_Q$?{71R%P1Wz1J9o%)dnQdc4LD+7S=a6EYFp>EzsI|K;0eJH5B077hZmSN%AqPa0 zjd}o!=KGObPuX>SX2u)wmyjr3e)q|D*X?WXH|ANhN~};O1Zq51dKysY5jo6Xi zCO=$#MIO~xzq|-pu1aZuHdrdIdtgK1X#*lyP*fD`6$nAr@h0@G@wrt`!_c=LIUw^s z@WPnt^Mg3o7&ET6CRV=u^FxU0o)h;>gngkR`#AGoVLNbb+|hn5zzGzAJ<4qTs1t@x z723~u47};y5QA8?Xa_Dg_A61(GkO)YMwOoF?W8Aa-wp^`l#2hoxj>-e*6zbaty@z8 z4+johK-S~+rC@oDpq_gkEu}-0wEjidRg~0l@6) z4`0wl;&Tfe3x=?aC>n52mJCdy5;a757{1%h$W<)-aA;B}0IQsy#a5kQV6SkfD3GvY z!wrq^-eQeoan`pLkBq#~G?xN4Gqlpad_XBVSoJj8J4Z5riVijh z@0pXuJHgw3>s^n3X_`Bx$pv#~CF~*2$}oxOvsQ3`mh3CxA2{WlFI4amvY_)IsB9Vf zQ%ofQ{X0J?hVb^a2zU(yd4D7YBaPdcx5`#m*P_O;$mf{Mf48hXp3UJfJf0}hYp&yJ zeS)zwz{w~1v2$&VT|nR*d~ut=;=+DAP)7qSb}K^P^P+oggTt-=TiHk$V%K;Qh>s5f z@o_2_S#b)Cp2jcGiKA47Q?4{RK(3HeZ1d|`7p)&~ldzMSoHc1LoRCoKlv3YFxSD!h zRr1ZV{E*i0Y@pnS{wmK)U;p`Z@rP!8A}Hf~F|9p-4&JqjL_$BlS0UB)w}BrmCm`lX z{+&vN#yhtTG4M64R38r(6l3YWt`;^X=A@7V_^?qTE&g0#~U~7B}38G<)BV zn>(58D4w)MUER+EQh&av&VD=Sx1{{9$rZnR9-EVL4tz@bV-M)`-Ah$K`$&yJpvy~Q zV#aoCF}Ht|u$qzz{d~e@DzCaZCn7`?Jc(-}MD$nw0Y9$^G_;E@3GHeHD^eh2OXos5 z1GbpV^`%B&99PWDoqRM!X~28|RBrhEZ-;0L-$jYHMxTQsWB zMKxtB1ex$KYYh>Z*t+y-_M!Q}5Iq({hpL;HtJYp593@}EzutmX(MjByCK_)E5*K&d zw)lobQcKqk&Zl2oc>k9p2{!aiYl7TRX}86;FayY}X=foEd5fWoX(ukhmSiFmv)#od zRXJo9>g&t_AW5XS^$*jZB({#c*g zsqV6uAZyesSk$GueVY3Lg5+wiB0yqABUN$8GktH{X@fuN81Xn=fKle-gzL4BAAOwl z(tzS1ngUkhEU~CpKI5t?^{8B>-$8~ODX4*g&LY1C=ZqCxUT`5d)D3;&4uBw4OLdA! z2w!>}kv^>3QIq&4pOEhv<$!HTm2y4s^P0r4t5=LB&&Y8Lw%fw?j#8^2Qg=-j8bJs0(T;$&-Nfj>~ir`|($lzgfhI z16@kU%2LY7{!yI)WZXajL1Uf>m}Z)3Fw`;3y+Jn__%!=S%GJ&lzKM)3r}26RdIwmW z3>+!Y#^W1e3^k&i20puHwU!MVjTw-}xc*zDx{+j|L$JW&a|1KZZ)NF(AeDi8+fEZX z9$9mEJp_Ve^Wf;aGrQ2ko`4vxYx&pgeC_(AWx)J#-cJx zjvyNCAaFut0v$>}th}QK+9tpMO@#2p>y67m8Up)QQ5C|A-BrXiZZqPlVuQ@#4wNYe ze4lsxv9wOflR;cme8u26&==Y9TOrPYvL;Z;z=Pplfip|Iq z#VvR`;Vg7o6abf_n4lEnbUTq985^pubkK*bXoNCqn=>hF$+&+f_e+Xlx+Y8<#o(S+ zsPXKc;`V75CKu65P5$I6*)qQJWbGcvbKU>T4FR@#OcmPODTKoWTA{;fF_esVm z;K~C+Hkdv66)Lx=Li zI<0l8_@M&g)DMvB&LiBEdNG90=C~0F9k1~dE{X+62MD`tGNdZNN^>}e+lTUq%AX>B zIIYdwy8qBTyqb)w`yxvi{Oi>e5%|b!8MJB8zvENm7Y9?&w&M3qC2*}x1Ch|dYag3P zsAEA2-GN%K*_si zw-fbbIo!0)sl(4f;R>3A-cU1GCn=VM$HWR5j?cr=i$=w?62zEYw3NTJrjoXwx5oz_ zza%uiIJyC-XmH7_sDE!ABz|I2Ega zvSNc;^N!SE`!9=m_5-934spC}0|m2I!5h{CN)@-JyOvOJP0ncAx}+&}$#K74PQmD8 zY2WNFU5g8E2y)u=SF?iYqTXYBQ%q-F=o9TcRrmD6ArR2r)d|!~Qm;`x^UkF&9fP)Y zs$>Z(&*VOm@j27d3D^ITD@CgtnSK?eHkl)340FWsz??wm>d*K8Vqe_O+{+7U*KZnV zY5cOYH?UP^;JIDM$cUgNV~lX{vyse4Jf(r%NDj}27Dm6fHjpBzEAVxa?l zBUbe_Vi?)r7!{$jPJ0-pBnLdJeQE9$9{5);FMA2s{OXR@98N*l3K#kPJn~e9#D5+q zpwUuoAzdt3}HV?%)C`uH2~` z8;p4`IUL%o-KEqhZ(cx>^1BU+T^;**vXuFZNin9~Z`se$>iJ?+m|MYQF{F?Koz^?~ zF!iWP>aW>@Nev)r*7*~~?nTN9aSGyU!g7p|iIkKgxh2`J2w?gaY6Z%$OP@`KMJgKs z0pMy&%K=?O$4oS#h93J}r6=XOmRFfz&=k418q~c@j#M)Z3o`nIP4nncDq@OJ zLBw=8?dFDxi_}1J%<>X%6+P`P}IkRe;yOd{*gG|C|rDT@!UE zUsE31x^T~Vg_rV~jJ1$(y#OvjC?enXL2N@~Q(Z=#hhR1}Ej1x>PVn|vvfc=$xxPB* z6s!ovq<9Ccj%}sZhl7P!2$vpd0nIwOnkgdJw~69HpJqBBR+%p?2x!s@>D@ZR;5N;x z{+C$O^H$UP#UER0$L-$r)F^^-j=!vq%*>HR^Q{XXO$AFt|*PgO%8+gw{rxO+sgx-8dRpDA)zqhr*AQCEx zGl>^Mn>WdthF$sYe2wODN!}Yw>f7O0-b6T+)2zyunkOIDNE67BSolQ=qfzty%n7v z8M2*@KB_gcxU=0atkWG7DA=y8zD{pRfkM7?XC(I5%i>-Ca?$%j0pGX+#apX;*yy851g0xtml9#IM>sSs{{Q^yd3c?-KZK(Y?lm!w|ZEAouOv z&IQ%)?pNKXnfAEF3wcRGpr!Ie6Y<{yCEwMtr>}A%2GW~uhLaZuuN$GN(~YSc2p9JB zQ!05F&b8yXj{U%uhFUa8zJRU9FXLb;&L z*(dG?5drcp;O>rlTxD`0p%E(&f(^kpaSwEE@rM^ltr&{@;>8GP$0{d zMA=~!O{P!J#MD+~SwD9V1?;(+_?HgrZRg6-Hy!3*7Hw}1l`1{nQE9fG?z&aoA`d#O zbnM1Vlx4y*bbjV?WfR5)2Zt*V9JwPjizUdpIn_xSVh}&d7k?f!I=^o!AT^i7eQVO= zue;^<)eUKid_gV4T~=_YI61Rl^Xe6@=H{J#MbxmQleRgt#w%xR?KiP9@@NBc^NmNFy}mWax}VcYzFs^nGBC}pOCK}v zd=+6(fz!UFg507NIuR(!xA$xz{q$LD&Wyt2!lXq$_A;KpVMtxgPY*Dq;Wlx&Wk+gp zeT6OQ+6muM^W4g=sGjrKyqE4oN7k^r*2E%lwD1?iF=bef;6dFUVYnJiMxUhBIDVbs zlkChv8YeHd0K3S-bj5x4^SHCwozt4~R#UO*6z9V_`M|rZ!tRyd%Ck&-hJ$Bt3-8ju z9Cp+tKY}zW5kA-MebXBI#y^)HRskAG3b@z1N@YhPTrxMx{Nm z&wZ7m^s~spn+P+Z*|8;^qklHMFC521&!pV-O_l3kHhiqC^O~{F`4`0f!k2^jmVK zh+9Z}pYJd1aLC3v{2DHJkc8B;{kf=F^cRT?w^^EHYf;7bw7VBUH+I#`79NM(Y&)_M z;?3jXc0Uz1K2lKrLnhJ7d3KdH_yZY;IphUev*Ex2$RXuhA?+m+3d!iQLgbq#?$yPvuETtZsAN)qhK#E?=}1a0T;8vN^HA zxXdk{Te#`Q0P=#FK;6YnblZBwQbKM%p^T&)#iK2CSJhdPfGbG+SAu?PMOk)!<`ceK zWjnZz`exBt+>Yi9jpN^vpR6$1UKesiR*F@2kzq^b4oREXrJ^;AGLLHz7yg5|BI}K# ziId>$Nnib-s>w-uG{C@TVi;qp~ zW`vEJv0GN6RC1xAgmJ=L6RAyN*Hg`vn9ukGW2>rw#?~59TqWbXDPh^$1?~$Jy~F5% z?fSVLXS4ep76t_TFyxAI92V7*UiK2R8QWpKbxIZ2^LF_OfYhBSVDM-L-K`Q69g!bQ z2BM@lh+vN5KVmQ?9+u5ZJIs05-$!p0kmaqD=uQwiG;TW?Ros)c31JK85~uZZs_F<^SIr-yyM5G z!m$t*7|-8vD_MkoafZ(nFVX+Sky(9=6C)7eDfXu!Ptv>YR^1>VF-0a3opl^RR(TN6TOGW{Hm4!?OeG>!v9dDeE7|QU;rH@aj};~n@|uO(NrLM+ zjwFh5YufOh!7B8qR=9||_KQ@`J z=Cs)E;sh`7R_Q~nd9RBLOKW@1e4(c{`AyU`ooEs1+8bjL_&qr%jVeNCr08Q$tN8As zv47=-#ki42qEQ-$Q_&4Zjhzkg$FH2Fta7tY7Mf(kLkBb-Dd;y08z0}Vmb8C%aOxT) zHghXd#2pfSc@+p!yb8#GT9%HQ>Vg*wmTUIkg7^?A+Z&|j1k>m*S-BxvX!PKjsa;cH-O;+F!gUXp3=M2gy3`g)bNt8NceaWq2>4zCojBD(D)MSz6K+_<#g+je-A9p zBSH0(PAa{vi{8a^tk8*wU}OUt#Z3}b-=soFtA<6us6~-lUQc)e)K?lwvae+kkBcH< z3WTJacjk(3GEI+8d90Tu@UA9u2%!9g)DhRDDbh>3}KfHFU?x<=}O6+sIM zr?|FSG;BUKvjbc{MxzHa_ zrU=1qqQ?NM8@l6&OpCF`24Q&{OhCG;Gs3vmUD4p63t7>4?!8=L_yvrUwNE z*;-ePn{_2K9~B!ylj~1iU0t0&!l?)PoX{oIX~!SAM81YdjabLu&O-mqZ8{@2OTlaf zv{WB@S=V5Ea_qCMY?=&#wlOzXD)0T}q_m_Lr7 zYv8}1`0nPGjybLob^n;j#r}biWZ8Qm^=_YkE6z6jjEsPwZ>_IpgT2j~gi9+4VdG-n zciq@sJfAyVh`z~|_Nf!|^z;Z+Hqg@(Y7q7>u6ZMCV4dIfJ3%nNIxqA#4E9W2<%>gh z^Ag9lB^kp;UY$YkJEaPhH(FP|=cH7oXU#6TzKF3z;1Le6b1URiMglmQU^I<0Re7S# zKfv6citA^xasOoCQZd6^OAxJIZjD^Tw_#%CL3#+xX+biicAuRm`96}hNNX~ zm8C^ZQ(j-lB5AK_%?*TcQr`UZiGnP)B=B1Hr;gocBo#3||F|!G4<#1m1dg{r%hw^U z8VtE)p;W%BwHqAD1%%@+Vn%EIG}_=MdZK3C9k(-=>mS)?v&|^mysCKzLvU_9N}#;! zC&froQe*%6!f?`3rP*{MWCohpoNXSXi05J>eC*_09w9At?csY;QtLd6U(XY~?G1>; z>4_{E_cST1K8n%!XMEp`t4l9SW}5U^PjM^1;#&X5sS@@U{$f?MR<_Ki@Lk+)ukr%c z*^rug9g&!0nA2D%ENoDe&J**9RPe5(2Z)jQz0>k-70< z0w#)yLphO`)d^0iR94QsL?ATa`4TGYK6`k&Vdt}<)gvw%OqOJ|>dl)-Ssk09sU5Eq zH%Va3|AM8Wbz9?Wl2|IIqL)g{w%)Qq_dj3gBGXfP4VM~OHP=}Vbh996e%g)_`i;k6 z67pUKg@gA=!5c$P>)2DZPUE|pv$d4w;E?I$LztOIBVGaFJ8wFR zltMe#l-GBEjda5I;L!?Ps4OQE$5od`H1^?U?LCADgI%$`wYWyEyp)C3NUT~X%-fWp zK2@FHR_$uYcqtx`Wa*-Qf0O#Mx7lnxYg%yqBZa!kLo}vC<(<3Lp^hjv`#V&8pIE-!R6|}Q~ntR^&D1+(c ziz^vQ`nv7c0WeN9)lF$Lq;jP6m&YVC+a31a?@I_{3>jPR^P|Mpx=#&LS9nEBNC@B^ zG@P$*$EWu4yEnO7dm2Rka+Qqj#NgA2_kbXYP8Exw?_ zdoaHn+o`rjl~baAS7MzgBEHen1y1!0(2dE!Hi!pq|4= zMC;`P{Y6W#gtg@ue%j-5?az;<7D>i##ipg#ur1U!+|}odEX2Hl|v0B zp_sjKI#{&5If3K+NoZ^jktKtX z&>5d@VFC9p0+M*J*8BMaGhR0$<=I~!YXU-3g2Yu zrPi1ET&rCVy-`e?Kw|3S3%!cVlLKuOk_u?G$Yv~9>dY^@WKmIgXx#+9R7jNDBcgdi zRCiln49%F!Po%X&KB3~8z+xPEs!M(n90Cda$lHVdkQ}$Ht?m|I0^`8t{ zFM`IC{-Y5WiUG=iuyNb6C#cfVo2(dzEzj68lY2^jQxa5y43mk4_ajXMD&Bup#~KMz zAefK{m|?N_MfYI>d15xm52sC%!@z2roKOHX$Zuj8(NEPa8a@{@JJTPC@9AoGRn%Bw zL2Aw${12-&?M_6$em~0eSxN}g_k4O+sd(f#5`uQfPmb?A=4c8ho_6=*B&zsq^GAN$ z0%uD_WNYH4ibby_!nE!^kgF58NzG$p#+Ze|3l0OFDP5mB|B5X7|b7!&6;RdxtC=n&slIOFUB#`()Mj5T#zHY^2 z@hbGHOhf6bQ;(nlV@lZ3+Jc=FW{Fl%9`6qUq9v{hbQ2U`O9u;A<|7<>zgOEiTJ?}K z99Cl3x`{z72@DGfcbJ6b@-k6r+7H&!ES@ZVnh5ond$@c`Hm*N@FDiPRmr#R7CnJNf z`eyj&vI_i%R09Ff%kzeYdR_|ZA348bsTrtj#(9(qe*I`Ioi(xL+Efm`q z-#xz?zf&4|YibOC)lJ9!9rIb{QE~b7n<5txsrs+&Pw9|+pYVA&#Ggz^X(u}4<+1v(sIgYn4T`xfDZebl{a4fhB*g$!~@)HR?SnF&j^2T@5pq{YyfNUNw$p(pm3T#Rt_#u9c8oiB zk8}3@1VbCWxDmVn7x_seEDxO~#FrpHkhio#^Of8;sGP&=?6~>4sVtFL*mcbHZ|)w? zh{%f^-ttAwy_2ocqp6KrJD>l)a_20(_CAI?G>GsAMdKB=kCSU24AYOrppUgCnv&ko zziC%zVeo!WUHg}zLs}n}_fg=z?Co?J*qRMnBYAQp_GQV}dep5o%~Ym8IyxNG%4WsR zWP6bRe%f8W_3G5KNw6m(>d81Yp=$QY!_F|&l#7W$)`cW~8Tw-4{8pcs0pxK%UHM@E z_$TGsOHPis_rf{Ri#V$0F4_FZ#>(8u^4D(=!+uMA$ETJN z(O;-pdPD2*uV$T*VPdWSplx6SRY!oubp8$uxG8eY<=$Aw*Q(#h5=z(#?Mk)Kr8N*m zA*MlP8B;xMq0&X&)E0v<4wI&Ebua4k`7#;{65m*6Gf(3}`w%ORDG^=`@9pCU?cY zjfJ!7VQw5KaaavW@B=(yl!BC}XS5YM|K!Ru708B1?*|0Dab4j^$#5QKqKo zl713bo)Fh=@ls}ZQ};_->&4+-EDPn_LhzlSgUQ$lB{E^VSz#7P_b<8*0 zffAktSrB+ZDlZ)cfO=Dgaps4=+?X#DA&;6OEl;!-p);4)aWJ!O2m%78(E0)vCJ_+{ z{gbA5FM85g5uT5W^Fjx6!hX_wQk{j*TL1PY7)+HQW2&Hy=kgxv9XXz;0Z-BA_lKJZ>|;BKbtAg>H3~W&SlFV!a=o{0 zxc6gMi&xY#(30_Ho3sn>&l6wuyUH`Hr}Kuq+%$BLD$f)sXt4bjQn=Pbc`dtA|EmL_ zz0AFsit{4~HHfYIfNU|t>WG6@jagYD%lTB#_R;y5{e(`GRPP<1(3&KE>Z_<1iVko! zS?^tMdI<99yyd~2>0o+`WhQp-y4+1b0s3TsDt_iu0U$AydKZMBqyVt`om+*!j@0DM z67?jIoV?5TT-xLgSZP(H=%FT7_oEj+_%XzCX${mMHUR2Ks9MKumox8hX__Hvx8lEZY}7;^arUi-K==W}r~15Sz<~K}H6XlgIU$OW z7gX(j_6Pp$810Z^#j3y@#CJwx_^@U>C89j_>rJWN+CK`2neu|xFDp80UW2FIS)#Er8}uj@)5yf3=Sp7JN!h4;2}&%bQWh5YW>S1Toh zA=%ci3BKxFz38?E3|CXL6?4p932J%zKH(_Y4VcNQ6cTD ztPc2#Z~Wd!(atAG`B=rso5mkm#h;A9E1y+N-JZQ0TnBBlsng4 zWmr^Q7ae9`=23r8MH&R86=vuX=~n3&T3SLtM9EhgQ4vr<+5ik-=6;9o zpFTdwz31Gs&)R#hy%xb$VI*Q9h{>^_A>UDGRwky4JS$fYSj6s&=EvP4Qx(Ry)S6+h zdydDWp>kzQed_tB(tfn_ww59nsyZTNamlttE>M7mhX0QkhO(z&w&$3BCu2JmrLFcG zn}3E9IS+AB_CM@;^qAV3+Ie(5#B1p+hWT^-Pc*dSBuVKR&76Nvsli-`5QN=9jd$#L zO&+^&8_84HEbTBpfQW(zLEu5wh*bDNf3s$gLcR0Z>iM-v8(XRO6Xg@F3uZf42+B|3 z=DX5<>0MXKu+$oNfHv#8#_o?b1bHK5f}1vYsp=n-90}`KMLyt49M66AxR`|13f$ z%O0SA93Vk@o1c*L-JH@ATic> zRli;^I^O2-KDrOq_KJd4QTqGgp4rFfW*6w}V+`&6m{z+rIF<=qAXVlRh$~@$$XMaa zFmkx-GceuTbAcR9L#*5_I?uzuyOUwcy_LpXz{L3CS4&&d*?&OCNs?0+@N84qY$$j~EyLehR3sd<>oKZWCKG>|Z#Rk#bp?t9OW00*NV>wql*)*>2S9*z*$ zinu5h3ptzMDOpNF4o)-Sd-=LuWoW+_zy2CC{OFjpekCQ)pszZ&x*bzC3$#5IxUCj< z*P)NYufPiLl3IA%LH{vFC%^kfsQ^J$_RU=2Wa~d?BAbqgOzI`VP%*_w=cmQHFVbSE z*T%ZHbWmbBj!7987u5Npmy1mYLtK*K86Wpq?2%EsFNu(=f^gN2_2v=SRZW;3z=tUq zJE50zZq}4jNfQ)YLmFAChla}6X7PZ`XE}62qkPCVH}pR50}wKsq8F(v{%AwH(u=M< z72J%b3ADoBMX?k_!z={zm3PbZc5}_``8iaOghWa*FMRG);Jg>R#0aekG2EVub6<}p zLt3G!%HC^WK5(uSsqd4yaxSq(uWbg1Am;Q#;Jrwm`5z1L*ETA$!4*}9Z;4ToIPukf zr2AJ}nH6KmHGz`IEsu?}nSJQ^87GH6Z~a3|F+46~xo7>NG+cJBsSaSGz62M1tIP5Y zkj`z1G7w7}LO3}gDTQi)^xiblCKck`(6L~L zfZJ)j))nLp7@(jHKLR6w6b+O1X>8ykmLO-;wm4L)*%T5Kx>P)6G)$s6qSGs|ZDw=x zdm2v53W*wQhN)`~xdskXNwYKP$Sr3Qz0vK7 zFqtMgF$lEwLG#EEF@Lwpn-?+vd>uE3i+wdL%OqKz`P*>?cl%rfa9+oT8vMOWa8>et)Gw)b45_u7xMl=7-K=GDr#>C?w;24a%= z{cr2fF}iOX{du94BEwO*oy8IOeImvoeToS@MN zhbQ%N>kamJM7dYgb6q}sA;gXRHUu3v=_T34KfSMgq*RNe}vu9S5(< zhLVb31d2?FWNbDMWTM{Gi@f1x(h1`e$P~D}R`qRFJ0Ycvn)$<$?Y^-*w^SunU3m2p zdLQ*lWd*`dsi6|l3pk^HD+UmBb}K(*&;)AMrBiX(mh+i;pOQQPAEAQ%uDaYHBn-iD zX=Tx14-C8{+~ia#gf8j7jY4Mi^{RP`1MnZdKKWE*`zR*VFvddg(a2BxJ(nGa*bh$k zRXFv2RS9*z*cuM}N@-(#b!@Q^YWzjv>;j0oNxiY%hjojYPMc~VL4_pRU16jOK=#&p zSU-7qL8ZH-&)d@rOaKonj}F&o{AXooLQGmI7E427lD}g5`(3Z*`F!(lJc~7jW1L#w zoQoq!9kQh%EIG8KhDwk)bjEcG=*KDpmj$9%4?cjPm!%W`0*|+Gwu8IAAs)Ot8wRH# zM-AI2U}dea1BrS1+=60+g0xzkm0JlU9-yFe5I{v8Tj%1=u*xVmgjLVn7GLx3fo@RF z*`0+-`}AdXQ_G1&qpSl-_se^4;Mb{yT5m@ReJaW3ZDyf^+~ zzvDxGL~8Bh|7fp@Eq9WXLeRu-dnAz^sMcufM#Ql;+uoVh(Lwxp1LI&}R|^(eFCh7# zlGU@-wGphDH^I(hT2%NSz8dG_#ul62mZPlpX=YD<0>?WC-Rw-9BR}_qeN&HMFJrZ?J3RRgJyo_7lcR-l}Xr?r*MTmy>y0lf~ zQt3SmOB#EVlL$7cD8H7#C_JAJSiKZ9~)16w4t?J;g{;ul2K#U%v>UtZmF9>UKk3f}Tc>C1Qi^r5fVkJ$AQ z;moeJMFoqb#yCr5Rh$xa$9#PVFR`lC9dogy9Nx?OgdPL`1nv`|hF+^ZVKaYOE(%gz zi%Y?O-THbQxKDg5Kb5@L!OR9yemz6!*R+=K_N|o^I4FV)(=*ZC4UKQT!zm8X&2-3B z#2@JFH|nd`FgBpY@#u~uONt^Q>G@U6XBbUU7DuVbburmkntwXd{=7U3g?DF<5);l4 zyX;r2`0$$O{N2+E7tqf0c6Uf1qO;9Ni* zetFNkK2+DBuw|{Q=9FKj48r@GbHh{}_kl%N_AiyirF#mZM;9b<9BeZc6h-w9Q82#g z0dZcNPTs?_eZO&bm%2?Lkt-(NZ~uO14|LXaQ^KW8BRJDSte+4hD2p3iv7BXpV(gYFfy6yyiT)9zM|H<*huCaMmuhJwZ zUX7`oTEjh)x6h|~W~l>qu}^av;dlR_)2Bw*Z@7n^{{kevTm^4D|I* zMNTGR=7ics$@7`ftfFb8(Oz4@9~Pb!JKwN5O~(INSw3=F>WlQ9m4u(L)Dz6wvYh5H zg;JSZ#|eBm{K{0bggpkginuLjJR?!5puyMG zGc-d)G>CHPb`H@9V|^=z54fl@QC6@{1%V%V3Hd3>6YCs@AOI}(HP@!~W0^ z@(kx`i>C#$TudX6|6yntVl&{zmr@%#+!%zP`RqAqqy9b8Giu-V5#v}*?Mn})8bQJ6 zw3ocXHmEN3ism8cQ^(fEMZD}SX9?z~)i)q;AwFW&f=Wbxd5G!!ayfhLRKmntT-?xb z4+e*Aj1y){4!5-*E6TBo9IQF;@iwyMyw7XzKj}OLo>*sFF5CSt?E7z!*}`G+jc?Ia zoB@k?HrVn3_0>Yltq(T%sk_j;f!7lh_tW_CAZBwpt7b3^3o2-)w+v&Am=*u~9dQc+(?#Nq&+p&7=X{puY9V`N zw4D7TMII6n?Tit8#8%Y)rb_4S%~m|Pqn@W?65`yaLy70~z`Nnt#<&9_--e(scFo`4 zNh!tldcc%PHoT|kh95Gp+1snOw%NUp{+NV1UbI|QX(gwt)Wf!O)>=*`%5(ha(f}Ic zhA^2PR~Bt*>-e0b9I{Z}F20MWQfTOG!g7``?&lZbA!xCL3p%aRSAIP%Iw#)>MHZu? z*{3HPupWrF<*@yyoQ?#KFE@w;24B4n3SQWE<|HL$ZVcYWYpl8lhrL1;ps}25QS+=^ z?VuJTQYi?>ga%sxo@E|{jNbl4MXajN6LkshO96`w0Q)^?ag8){(EtKF?9&k z;Yzru$7AUmb(!0_zzhEw_g=8!zZ)KK#}si~4m;k0r(XF~8$UPkH-tr9&{Yw$kubcf z^@_!rxmh^)(CD#FTDH_jf)UJ^KVwxnWaFi-K} zU*UocH1j#wn;PZK-87Q&Z4=4%pv+Kj`QtMmHr8Np@$8f1(ZA+91?{Oy0k^PhldSo( zEY2M^OP@U7D#wxAP=`23K=Yx39}9?KH;u+C(`-IKOCbHJ+{k4vI;F-9i>?js_csvl z{~%r&ca{9YA$K$T$ix5r(9Qa=rvxwCct`{{xH57dHnmNtF`oLn zW2d3@bZBoDYR)9P#&`4Y`0Wo9e+Ky;k2mh(!Bw*>ts|!(Cti(qsC-+T zU}9rEEdw`yOHg5Bw(H(a>|-J~g@$88zOgVc-g;fs8M?UkrhPuo)rChJ&qpJvZ%z@I z&)Akr%aF;gZkqo48-`d?JRRY zpnJ~tn-O19bdgSzQ<{cg=R3B>hMLOV{Y1uRo`#!DGz7H#y844l_^;{@2~yu!;_-6Z zqKTE1jj#Gf1=;q_ZmO`Pm28OkzFiC#hNtuxV%*)?Iq|7F^bw_=I<;hmYb`E88 zE_?T6ETV)>blJNk8o>)h>3(rvhbk=D-ZnpvZZPU^=mL~eyb5k4tW;}z9@TwYcr|U$0vp9t{pO z^l#gU+dFQaO4<@#3`1=~nG)dde!}YmOsRE4|3M!jy4^*y_ZLDWDo<_;~*nBu#!FSN@G-de{ zpQ53LR%p(E8@QO zWiVR6WW}+du84QCyo4bH9V$92nqxIa*Z(y;mVK$2?9+_oK0i4*e`p2@F$a;w}Nam)zheeLjL zf+wHf)@{hrpwRRZn=@b#RG5p0%|z{=K(J&88E(XSL=R4z9zRq10JIB}&v#M(oaOPESjdC>^TX51#&>P*mjysvPLC%NoJB&h@S$0zL3cmY;^H?Dti($d!kn#mu0Co-MZt2>ab$P z9X_9~p{l96vz2o$6(gu&IbBjy=TcgK&4?!aRe~f)^xdSsCA`^~W7_H*=2#Ss^8~_C z=?ad>F`8d6XKt+YkdYL2Hb*Mpuc^o1i2X;3`It2P9VE71HsY$$lFqf|?z@&;GYk4A z6CR(&4!vOk&*lo)GdX1CQV*UBKW=`Xw$xvn&Q4P~4Eq`-?{aX?+v(uf6_p?2m-EEX zM%LMjQJ1h>gzhK#n-bqLIo}|Y!_|^|?pXbbb`g<;F!gy!W-4Rp?PEj#TVH8g|9htG za-%CVs63p*dOIxHtS0* zW$givQr?sdHDGA+@jL$fa-KjfVezY~l?flA0FSRUBY&-wb!c0mbnO!1bJ&Mlsu?g7 zH`0VTX^9_4Yxv!Zt;>ZYhY=*Q818N=Mo6Omp}C1fNSjtfnpo~^DrD8jWm6E6VLsoC zMNy>YhUOZ1D$RVI5uj3B{o)_sXya7fx7t*Ym-GGbx2y*1A(dT#9bg_FXe&gdj1N3}pQjtBf3^Ld z@yleMaNZQRo%?n)v~y0g9Zm6eXXu5NCy8oky0(AV1dEkBZ+x0${6&U&`@7+r+g)u z=;jJFeYHyZd1Y6e zQwuTNvG)JXa9scPB|WH(lmmxhguUV9z7YGF51Ep`*C?9GTSo4*8YVPQ3piDO2T0J`tU>aX*12eaYW*J^x95vGynZh3@5Aa|NAd;<6(`?93icPHp;4+#<*iDp^UqucX>iJV)HZ-qIuUeP6i>5M)WeJK<^vB^i8^~&Sn-6!*^Q#YU= zou`6M?~jFwru;<&JUk0pccdblOk}G|*H80pKFmH*wA3tnNoSwy7sN(eN z5zEMqqU(rG-3xbZreI>49-APm`k(rOwmX~SuPCb6Ig^yNUZHWPPEz;et$6wL#Eu7^ zwv760TYf@{8XPEZoorWCODRE|RWXT6tQBsbxuOT3#rKjE6TM576E=)A%IAD5*zsLe zpsrFupJ<^%a-Xwa^3&^Ly?Nedai^7uzU0`L9Q+Al&lX!sLWYIgB^7}+2ot5Voz>q*V0sBxw8FD* ze55TVF}|v7YAh$vOrP{x_vZvfnmHjiHwAS(jEGp{HlY&1 z;#Uu(t5zTd48u#HF>Fr6sry(-XZlq!G^fi2%$Km@vB$S*cC=9m!{s+ti|Nz~()6aL zU8?55zv(gnR~`YutZEWR%&^8)MyYq~=j?xTW4LP<8Gw}W=JL&_FvLS{nG!Y>hjfNT zFDF@sORIB7m{o=vKUGATl&q9yjx!f(hb3DYp5-?J!_dSHk9_8qJoRI-hJE%5eQkjf zHs>ZzHrR2kpS?EeAIRO-)|O(onnlee8A3eh z^*CYz*xq9_;v|XT_h1?@)1uOJxO$sK#Nr-_`dK#~_g}569v6blTyQ)(i_WWm`&JH3 zo{PdMV8K)hjQp0-xE)NfDj7NJJOn}Cx1{;M4VL2W+nL`AgsC{`W+hZ4jXfFDFWoib z%+3kEPxF4+8$bAjkFHw(4lJ2mr9K)Ds#DOvrTU#Fa z4bfhE%EE#U6Z_lBaxCV7I-XQa$F%O^-Z)=}z&^}WiZD5Wq?92u<|9KZ0)}alEJ_^X zD3z6{u1dXf1I-jE9uD1gFJT?KZFnQn1{|X zISG2xYhlDN*eyf8g=gL;RwB|3-Qkmd;W59NL^t0;OH&WPj~&^5tUHifqC}<;jZ!7l zzhd=!^Q?L}>x2%TI2?vN(ZAZLqtWZ5RC9O3Ee+`Ie5LJa|6tsvO_~!C4Nn-(aJA82 zqe<|%!lOH#i@Ggugs-%Yrl|N1oL^nD_)zPc~e+K zP)%hP^kEZaspNL~C6Hhc4)*is{O#j@ldRv}@+GLQ0?}8%ps%YJ7zC|yi%Fs|Y+2q} ztiOBiVk)Iv#A8~x?iGRM>ZbY^a?~U&8pPc0gWRwm97ZX-vRI+waEPA@1Q18L`aQQ^ zzPDPG}37jQp{`d4GltaS!vzRn-NPsRZvESGn+rOuTp2> z`htZ;kVHXzIjnoC38-3pfOh2M3Lg>Gayf$rq&&pSY!{wONr8ZIQ76Y{w8?*MlB|vT z9zYoOS&BLIPu{%Y{+(N|L)M0#);QOI*tpNu?Q_kL?VcIygkKSyVDd@n-v#@(hQMBo ziwy8b?ua(gz*2$SHg9gvX$jL1^ui2vi=aojb?7{VhG8=hT=9pf5Z=B#I4Q-St zOL33?CwqG&bMGqxu3U$~e#f-aYaHyFFXEZs(tjPEN|hAVLgO^UHJt1w)8}UXAfHA4}kbtqR0T?_BorYbt~*y$609Kj5c>@E==P+>*ha;-~<+ zFnNz)Fe(Es=%7Xr&067V21_j&XpV5@f^C#8T}aY}*hcdRn}Md*Q8vHV5ib3W zn~PVG^gbUP$>cJkK__t)_Dfi=>Q}Mn)4@0FT;B_6A)gg#{=~n;)ENGG{@T|q-FpTu zzjQtTVqLM5%<$nVx(8oVtN>k1FlcsrLC}kAN%5eqCC13-{r4V_j&Z=+Ow;Ekv)Z{@ z7&vNdNRMeM#2uIEJ_UR%MK>^%_9@D^d_ zS?qh2sV6fHe~Ezp<(PKufO%lvr<<^9OX8753=M$KA+UcsL0M7q$b`e~sM1O&jmyPm zjzv&iG9IxJ_QIr)cb*Z2fv)a>^c!z49)Ur?|1~M+xPuT5hA$_p4D|Rq4DC`^mx34i z@tycC8oKz`h=!Un47kZi*ntd(2O^KFm`0P+qrr!5oEE95be-FQLDx%tHhJl(jD>Z; zt7peK2r0F`B)elqdfchZQ^1wwAqt^-4D0#48)>{dh`#vmK6F-09K=GIkA{AwY@ooQ zG4Z9+83E1p_G}wF8|q$S>aWqZoqR7bcE)7?b`Z>zt8@WRh$25waU#hb`Hg1IJRHAG z=)%hD5G;EGnhB-c9*bhocyWU|v{zg(dYugR#|6j=-7`dJg?^^tkL4hFi?L>udr)F3!LN3) zDwM%f10De@^uAykMOfKgsHHk1KE(O*!^va=d?Mq~>%yGqQ}QLn%Ss0<7NKV`doJ`W z%3$}*#cMH?ob^%-u<@!>rI%{A`2#Evo_&eHP*E>!$GvQCUu-+ob*tDgr7Y5c_fQ9M z+nc36%6YKvxFe)1n>pcVbh;no62M3Q&?812MgF*q;OvYlo=1=7oZVQ zv;uM_GiXN&n!`1D0R6EeZc&I}uPZgRvCeV$0k_8KxTdaQqTDLd$j|+qq#td)f~a`1 zkmU7~&A?_HVM&^=vYNIqaa9raiOIf)r5I!}uL`;qRY4&xCM#gdojExYoR8~Wscly% zp!ztU>FL4R3*rqbD&tgp=B8A)vO+ehn2+7f2;s}qKp6|)0jI&QVO$%i%le;ikr{x{ zqXvw1>pcf^ZSthmZCP8R`tI-D;|M>CTIF(oNN@|oPA;pNmKxJp(7KHIZJH0Ln=R@@ zZvfSu{|4Y%a#jxuN~8Zkb>&9MZC*R2Ye$C@d&toQYK95n(xnsq!g(XUk6tyjq|G`z z2Se>II6O@3Q)+N7bIc^Hoycw+HEzbj3@nv8_+aS)99G9!JX!tF<)ZOdkH9C@p{&5b z&^o8BI90|N-94ymGyNZ1*QyK0pFWOXpm9Cy<$;t4L8ffQobx=%M{_e z=L_D8MbkvEVXy0o3;Ji|z6*CxYIy{8%AnH)ho7R-?ds4jUF8GTZU`!>AD12hf5*un z*_!&MA`XZ#REP;NF)K&QWlSHK(!{s}6Ff0Xi*u$Yg1X}+W^O0qfHYwMqSia79DxbFWB_OX6eX7eOQ&yRSJoDQA9pH>H0#} zW7xMbQOpcAP)Ch58k=uWaCXlT*cl%SSG01fpOvbs56Y0Y-t-aa%NmgRu?v?p=* zvW&l+#__}b($hZ1>WtXDRcY>wD(|*_wdPmwWx4QY0744zY|k$>p#E@iL2{wYmzF-<4w`d4XM5!`=E1g6SP||Kj2*5B9MS-|5HscZnvV9nOY~&4%6U zhR*K`?1zF=-+Lwks1f>j*EjlBynyyH;ST-UV{4pFFZ@dk2B4NcyVQ$`9$c+@P8J|2 z$Hl3SaeR1r8i1z$2|zuf4T^>&B`&IY*T@eqsiJjm8kJr+z+OC)w`HeR4Al>6Y+UT$ ze*#05b9~{|Y9H$Gz3cYT84Zj@^vp=@C6NF%>i#+g+i;LVLfIMseQ(;SNRNBy)iyQV z*Y^VRK+!cS;vr@rQ2-gtBrAKpedY7e^XqO)U&#N~u?(HMe2zx28|<$RVR0Vfg&&|J zbpW>FT|%k9jt~i>p`~mA^kR0y^uY5>ObBfFaejiH8};-+F-?WTa15npf2D)y?ze!> z->~KjPW`l@mX=NoOD(bLt4z`k?3qZYZKg%B=Vh~X6Zkhh0ZcdJCL3+=ygH8v%;(8L zv~2s9y3xXHb1yRV9qA~A&VY{P=5xg=Nq3F}qW4$GQ(|*;C@WB~9Y#{r)4R>+;-pu| zYuIQcbTa6X`1w3k9cudMOYH(!d3nFXg@^iR3?{Y!AnIKdlCEn$A3!B~%jJ7L)3YX7QC?ku$)4g2_o;o9pS z3XP*{FDD)HBs_N+vMFwCF+}>J04-X>XB3ZK%&eb~020r9&|>I`8P}c^LohG+k0#Ba z0t%$9@yshT#Kv_R+%ancQTsa@H_a=$$uPXbO?8GhfnI~bc~2ur|3wa2nhlu~kq>|S z^$V4wUR-|8>F@op;u#<$`KoY8?c;~e1SrM}A-rj|I{|@C1BB~g&Gu!da)`7NpOGbh zO#^#V$jt(`3UfV(qZ6W)PpzQ#S1Rp1aD(iy1y{cJ!W!=tSQ~ z&+$z^Fz6V9h98HaDuek*A1@x2?*@p)+LCshM;Gqd4TTgh~D}vdxLlg!qw^g zNFVyVUGlp-%BMf}2O^Gy-F&!=TN7z7h9GFy_bl4Z(!4^~#Z^B8J zr~;qs3`^H~1xskvT%k&tAS_Jt9innnxQI2>V~ScUW)$-!LtsYyWEgTyQ&S3UB) zQcYmX@z7F;=*{}+IWBbpMA#Y~E!lXkJt`#VK2NTOzkruNgl%+K->2fsRiNSC8lhqo zF5$WD+q79g^KZo=CRIt%sM3Pf(&h3rfSC;<5a;D`eQXSc)$g20kY@v`D98bB)_X?R zf(_!ug~ldfh9kVoPu@b`f9A=cAspSTj=nQ=GNk;<0fN3N6;X$>sX@O(nRI?xvj@~_@PP@hzQcOZz$#aBH@%*@2IQx+or&lB|n4qt5Rjmh>m$Kmy6jcF&xP=+Vf(j zd3`bDCpfQ99@C=F9?ZN5O0NaCYKGP{fUhWVZIk8I=O5&37Re_$Xxp12IXH^kVkwZ) z#ZB0obcA}b@t(Cg-(F!ds)Z8CQU}qq1R9F$p{73hc*y9h0YIsL95yyfC3a#uW7;cd zfv1c!x1XD^8FY%(C)*g{EOrOfr@dep3Bkx`K^>xVW4jp1xzB%KKCGfT2wmj6dv&NV z_uxT2dakDLPn4+`339ds0F~RN(sc-o-r5Fo@TdZv1noV}DfCllYxhT|%n*bZADbQX zol4b9xgf@nQ2UOTT%HCb2L9RH5{Oh!I*_qZyXA{fkZOZCkM(9ndE@ftc)8s`-C4L;3 zAteT_MHOUkluJ^04M^~Wn=r0J+41IIAY9?SrU2%W7v4CD+6P&HUuF_7&i~)XENEX@ zD&E|^@;t1eGAdS5JH9&Di9^*liCW5akCe*AkcB!?!*wTA#@O0_D`V4*bXFY2h;rE- zS}tcn#EvfUbgpuJSca}3xqz#Nkr4o%|6M_4fO*6W4SkyE7trteol@QU6g7wG!{^VN zH=+bxKRq<;lw+ebAMu)jy)HrVZQRi>y*ant&3lmRvWKteM)FN;q0e4)VTYj8Wd%t4 zRO-8EE)YL6v=y7*ZO`OH>4heE}BX!|`+xw?aIe-36)*}_8`-Oa9=debUN@3(X{LpJ%z$tE2iNhg5iGxj>o31{`UHm zL~|O=+~2-GX-|d!uB2bg^0Frz=NnYtx0vmPDb2N+jV%`A@c4H9$Dp3ES)ZTMvwvTC zFQ(SwSvne;VnIMkZyCQ%BwL3XoZW3MQP zEqOr>KbmUuCZJ#Y18w8%(i24jjUU(2Ua8f5vC~zX*^Qc>mp$+Kb3Bp@8OF_>MlA>c z%XUwD6k%xx1Vw7tz8c?DYfrYbv!eP5mS5heOq!e(hcT zrWEDGzSxCb&U`kXg~Vw5;g{UbeT|hsFSi0?BS)3-XHr~t7*)_-{H$27T_lpOBygSLG_bK(sbZ(6ZH=14!+BcG+J;?s0=idE-MThCP zK)BKYyxdF+8^Aw{a3fdX{(QJ7T@Xd5V&M^FEmL{^+5P9MdJTe1RNc0 z`$RCuNh&6mR8&h(Ik41J{dk~<_%cIEm_P6kvQxOi8chE!bC7>^u_Lw$v-%)X?GX;= zbx!|kKQfLBr$`v-)!IBV%4PJRE_+sJ_xMeX;W|DK2sJK9-ld0YQ5zLY3aijk-ZA znr@V{25Vy-qJX)!o?LC$*n_h|xtXj&d!a>bc+RLYM=x{Bf{mW#kddD93a!_pUG_E{C=O*9O#W{NiaUK2q2S1pd^hG18bI^ zkqW8Rz!=~D>B&NK7&p~_B!9hzGpN}4t^s)PWH`>JkrXmuwe-41=;^gMoX;wfr@b%nUue4ifk zse^<_s{CrR`eJ(%_L(mG?t=+5RDEQ^an*vn9G0gs6;Gx{lkI7M#_?v9L1&^7W?I!S zHS)O!eSrgh?fM03&T?lNm5%z4Bn+lAG9nq9X4XFByDAr5e2EzNs>K;td_l3_D>FOw zXu&nVPkVux-0)t7ow?aO$;&8W^n8A8FKXYyJ&Q;4`No>LRup2~6*j?r73GkY z{>GcTdT1->3j{fReCJS+^>RBUh(h-E!M+i+Ad3dJM&?et(Fb^x@Je`bVFv}#e;iDL zW?1w31J%-&Z_yYZiEP>K=h^Re>Yq90bj*7!A9i_(mauM;XwOkLQ82}l!)0f(O!^iJ zFYYCefC5Fil{D+HIr4_iK>C_D`s}(V_}(odObqoZ*I(5%C^hz#6uJ^5Q&F%u@6bx* zrjAPtBq8s}cKwXqe-mJN12_Nv@v>sfb5ONB&v0aYBlPDEEb6WD6FN@W%K&@@1OE-^ ziz)$R)+Bv*-SQnMyn(WHP~)yjrO#l(?CGC^u~NG~-OWRD?nBUTMamzw%I+O!Lf1e# z18I4X{Cvjn#z#WAC+wF#c>+w%rc|Yzhd0D2q1iJtogilXZBytIuEIkV75i64EKTRa zKF^28ul^-)D`M~Rpkm9K$L_CqLQ~10@)cW;9s!v zp0>K|JZ1##0S7m;T6Kb84xQ&;m~YlxKDG@#%+#nhCFRlK(b{x9a=#7vdI&>=OtjRb znC!(c857emGYmg0(f8k{_$}4T)#~r(`daG?0vxL^v4#c3FLKmzCJ@fnfPj(dJr^kc zg0x`yS#X>UNos18hZfv7i>kNaae0c@sGV=9@u7~$nD^uQ&VWleLJdP)xo&puWJ~7Y zRU+ZOpzhG@FUh4PCmj`T4w69XW;{w~ z*gNc*A|G#Q!>rq~WU9u0ib>)l8-)ghM%z0UA+$owIw6!T%=Lg3;JrLvI_@2^xYZ@x zgYN{Fl<}N>kY2g$z;0xwV_jB3)AT% z;9#)4uJd2k0!MH4(1xKLCOQC@Y*ihOLHq;%*}ayxV}&P+}` zGpn2X7M$S43Cp+_Gpn~iNXb^_n-s+%Xyn0tG3(SX!j(Dw2)Fu>qH%`MJ(s(Jxc8~}O= zGB@=KxhP-ThWpDtdy-*t=7v~jX|*@t)g zXdDx?hmS__-gdrL98nvySlB!(#KK#z&9+#Ej=xvPOCz-Bh!G_UfVK z!HpiO_qyydSee@)+IG1b36e-7^P~&2Y@$M&_11scuFKuiPj;ahT{3wK0|lRgOlVgf zI<-=ff5c@{nD;iu*~6o@HRpK#Z0U^EcG$ zEAY7~MS&s{B4Cbn)g9fznOHS{Dm0LQ&gX<43Y-an<1vfxnrgU)Z_#XN|1Nw>kc&M+ zvGPVONdn45tvu@ryI*b7LnT?MrxO$N@BCt{A9#H@@H0)<2gP=sci}nf>sZ+?Upu{B zXmRvwBp5Bm*`Q}}LaC__X5Sr8_W$Hl!j@p`v17L)MuSpps~Ai!yBlf$cRy_ zF)JOK2W^^Hej7|M?sTGp8_ZQ)XDK0Zym9v51Q|&xl2d0ybd@GV)I;o?g4MB;-g2Ld z&`EmS=?CkTt9}$F=s#A`gbrw3ePGs-Qx(0h_W>H(vy9gN#in9lfqw!xPN|;(afFs2 zas?rQ#8u2nr#x^qwjgvi!(rcT!D`2=J)*w;6 z4=Rl47IiYYrR7eG-?+gc%iO(a-Ehm?Z|0C{&5@kF_O*f03Jh0Y8-m?SiME8S?(@M5 zYAoGTtZ-;;N0hVB9y=|6WSWQxqA5+Zr~M(5bX5@?PVjPvJLR2R1QE5$O2_60&? zLG6(>b1LNVYYp0`)+Y2X;TX2GZ6C;n$+X6I@pP$j3Lo;$e56bK{u2YQ%A9b^m>x@V zUFyc&_Z4-z^cxG02KlJyGuUZMn27((6T?KuNOgrn~2y2`8=Wrl*> z!EC5o2D^t`uKq>Thiq!yO4(kA^WVHm*6Y?Mjq>tQe&clwA;Y)fqr;OO6cuHEObq%y zEid%tMo#TpRrZ8pVNjAqc1>8oONuL`gU-29Uv0UnQhznH0O3drx}(0=cj)w~6XP)0F$QMWx~Y+=+kQb#`%r!-=k^WWLwv)$(YX_5#GfhdY7gllJB^9jARWz%?{qlb*NdjFha$*h$! zKcR}=>z(ptU4s>$I)*?h+VakOIoB=f#m~ozbwvNPS;~8PMWzH0;l~kk9$s2s+YA!i zf*&WuWO8knGdn%M*>hF1eLv@aB%NhIR9)AGC+O~y7=}i=L1K_D=>`F%yFm;>Y3T-$ z?hpx45E%p%2_*&;kQOBM5edojZQjp6{+nUWK5Or_?{%$vO-XNumHVoYh!MRZvVbmn zSoEuXwE1RYbeP)mG;5!k-A*F=$e{F7%q(x%(C*{v$o1Yvk&X!;Lyx1*JbVk6@VqPd zRT73I5*<4eDmc}TlkZjZSJB;kBe|6rdU!L0u`u6x2b3- zbhY#kn*d&O`mqI-NqNCjw;<9(-y!Q;y;VIjQ(?+wX^L__aoLmq&U8!h(U$oAlO}}# z4NZ%%gn`HkM}sWCzdJg5VSF1w-CryZZ3=DOgc|g`Eeg7?P2@K{)Z-(q6eDw-dof{s zY0m6f^JGKd%?s`A6;>V`o8yqYTwh*7Z0urfb92b51y{ZMgtd9Wq8P^xlf-IWg(&g?b=S*2DRF$g}~ zs+6_F=({HKujIpf^ZzL|m9iFE(X@{x+qT^m4aSY!#pjX;tIVFA)nPNZ(4kn)f`RnP z(V6ZEqDExIi=5@3>iX%Jv0BT+$yLEo^knKfZIMM6HwAvfwY zA;j|gKXWNCvkrfv4ww3ufru~VM>GiiiDNNM_mtgyco##g?ce2IIH@DdQXgrn;1lfe zTWhOSJbKdlGOuy=j|B6pU=ilg{Pz@2e_RKbCDoo6ySL7sv8U~ASIYeO@z4e)dspJ6 zR_xnM3eQAV`Ska967+9INF#Xxbt|xmG=6kZc+JuPPINB zijgwvND4->-EbjTE^|5b{OEEG!)4bZ#6|sjoF<(VCTusnh%S7ZBUWYDbLXg|SQp7H zX>_GYB4E`27)S1L%7@E43)m~7IY=AV1R9yJ9(Rh?L$aT;xurTxx05_$%h_>-**~1! z5P$xn;5?X(JuKaX<%n>FP)tADd8CBJc=xj+TJTYh4R&t4Od?$w=oO0PXHOy( zxy*XL=4f8)W%7Zp;rV1@q=v`fkU7DSBwgTFMXS+sIqzBr&ne05;NnI82^y90l;z6# z);+(MRK#YTdMK$^?-QS&bbqlw_ii?ke0M8zCN>=vzeF+X?XYnRxvbzaHgPm(SW{@T z#EOGD@2!VgXbt@(&&Y=ywvOID_GJ<#oE}@5DeZw&pEvg|D=iA~uI4U<9xy($@-{!Mo!T*`V1?=+=838)PFpzZf=?S zvOtE4`9i>6y=QR^eKh1{`1AZZ=RZBm;~bhT7wC6I6DowCDcXz0rBgWQ{qJwNWz97}%sxUc9l7PW@ zC#nPiIWwb|{^xd&o+Ni)e6b68{=^Y1ZO{yCVXK^prwT>I{d@^(8K< z)QGU?Z0Fx(X^SlFpu4_FHvfL7$)Zi>OnNI7lnP4br7-o%Th1HlakK;lazqGN%D}MNNB90B zLHWRM65kC;a_Xm)#O?)`XC$04w*{8%d%G9r;cfg7D9A9v6^SG+@?zP8$=xTk^M(S@ zH$L^LQ|8~iDMZL^#ncxwMq0~FrGc;;s?*RFtzm>0yipzw;!%T#vsvirDM`6+ z(%-;!o}WocS{7TY#0lJpKA!w<9~Q%Hsyl3!ynC7EOZxBKZG^V(e%f-lt|zk8`I>$C z^#ja9e6nC1Paf67Kw&esYXin zYGceEbk5t=uD{#&6Xu^izLr#zW0Q?-tPXYccwgvYI&LSzoYgI-qy|e&Kz{8(h>^XI znaUh|a5vjK;0o*JhY`q`OkDbv!qWNY9LCI|7~w8z7cXF(`Q`O0FU9aYR@M2cyxJr} zPK*?}`Z)^HH2=g#6#7ZJ2^qabGDb{aFj zU#Ci%?>W72hwGhQui^c=CFlaWByF|+G7{O{xkeoC1-7}v{EWN`a0oa>vl(E%t9~^*T%4wfO?>LD+ckC zD*s`IlRXn5g&Re5EFOa>9CqmJsP9bhM&NPG%_I@pQb+#`10Gd%*q)3h`NK?!up``7 z-k90_d`8Mpx^&qm4MB`1*_lL4r9C=)r72$X1qJNmHXUpSap4AOC#VM{9mkJ*rcJsr zuogm0c-0n>%_myoG%?pU&B>*CzeTIBDIZn~`>p?)ymq*8mvZAU8#gbYMqlbkfz-rv z91vpKAVB3iKX~SfSdMdSfp5T~D5B`66(H+0^ZZ^vMW0A$Emb=3m!)j;cC*dp@4wT9 ztoaG)q&L@W`k#&bSClgH_qPg}{DRjR^kwm+>G%w4ysnyY^gy3*=P3EQ9h9xHPD7G4 zMHwucYpoen*m=599hNHb&D;-KP1~D0hK1ap8vb^^ z+kT<8t<6vcAZk|r%A9(46e)axPcwIQ*XCEk1}Rce`An>_Z|GHW1~hC$$dFce6n~` zoD@#Y@f0TBQ%%i}rGmq-@NA37sy7ymg?g$`-k06>U;|@~o@zJ!CmVdLg_^aJ-C1`& z@fz6Indn9bL6@~VqAbzl+$a7E>=iUSuA0-rEIiS~gswF>=gZc_@N! z6`j&6q5s=qjgk?)c|t|=^2gJ!Q}d@qV8;2Nn2-6+Emzm9NE=N*2=W|4UMVr>;Vvu< z^>sDQVDP2Ew}^rFy6~FoypBT8+YdeVWM8cr@#vm*2)B7{{|q zh2&;*SN^g4J79S zT^>mWc#_TeK%tkN8i1sbGC}HHhoHm82L%K}56i4AbN6?n=-_>l|ClNKqCJf~9q=&$ zp?J_xRq*%>ef7@T-S+PdJyXY^0-5)yCm&ywj(deuQo)3UJZEgy`)Dt6JMzyk+YCv~KNST=eKwFHco zeQ%SFxr4Hm8KCb-qQZ%Lq0=}cqaR5SMXddd%cveaFz9VK<3gD3f<=x=#qJS4VVr7W z)}R^p@ZcxTcFN070=ov(G5Q1o2>9U9a+z}~H@&3Jl*u&2C~jN?#4WejN~wbj&k5h9 zLV8ezbHb0}rvlx zT#vv3i#nmYZOepuN@b}WvI#)pwblvT01NbFf_vq#Hp!3Uyu+^jBDB1mKb(Lt$}i!+ zD2-{B%Hjr%-t}ytr>N6a#=e$u^R}rXZ*To<}E;k4;P}OsvOL|OP0 z%5e0b`~p(5eddJ)pBO)PEsy<`By*wdRl-^R4z^4o5=B9vPRzC8YR>=-y59ojgOo|e zCuhBoT;xyf%wA|m#|;muc>j`%WaRtzxg&fm1=3{Dginm93^NxZgh(!>icsgKJy*A@xWq1V>JD~(Ap91v9Z85DLi z}J3oGCAXJ2K%aIcJ6gZz+Jo*X!zm z(E=_2hu;DV1g4$JgAwAux+p5b%khrtm?~ZUl-_08!4~)-hJ+8KYJe~;oKpgfjDI#0(?nBx)ii4OM|KCxrQP^O|-c!Z%>dS=kbe9Gs8-l?xH zja_U}#NM0&GddxgvX`!%SE!HU65u~@_rOY6$RsC21+1gPpYt{eQr_n8BT)mX^?EE4 zuY(QT^(o;15q&wV*W$LIX8-%F`BS-bK9e^M;mevBEL~Fbk$vPDg-r<{WvtC4{FlLA zx}9`Yl@7td;2P$8P}fsJIk_0#!r|>>k?jaL(=yCX=3Ai0tJgBG1*wo_19~qXp7w?= z8oCC4DUx40^lBV<&9%(`!*)VPr`tpJvLg~YtTy2U+euY&-y}ak!J5MNBF|$5-+uo* z{F(r~H6P#U3eg?D!umhNQ!QVwuYHSUp%uA`2Fhr}8K_4K)p_1dsQcn{$U!!%qp>#^ zxZ(i-;C5X5qlrJ8><;j}(~{CoHb*Cv5!*hUr+o-I-p%stY27_WfOG=*#}YhyDM9*V>{EmAmhkn!dpxP5|@>H^sJ3YR} zoLI+a#L<t3NfOU&{^6iSpXN+(`fta0+}CEEUE&X#_K+X?cLeOEkJY6mfP^N z1^K?O3`oB37e+Bs!9tdeIH@s2%$ahtjR~Va0@1l?HY40m0{!{MRO(0t1f5~Tc-Y6z z<5_pMr))SbO9v}jI-twH^}J8y4Z#;Qb2#aiL6`nxxyGlaWV6>lpA`jql%7zKRBlsH@q(zz#cHJE7hI6n9Be)BsG@z=;GzN3PKWJq=6L zO$GP8Uz$GcmzA{JK?pN46au-b97ijV{c6%C?;`f|JxahXm=i^5=mDJcT23Ui&)SZ8 zj|y4>Zdq#CqFRNyzuIKBA4PwIMM3l3C=BWDFa2|-VUI1Ar@S0iISF1f9%O}V1fIvZ z=Uw^B_dz2gvUgL(mF5|2@(-d{Y zH%0C+hel>wGV&_COhLEfg_c-#68>fw(^h z5en&@3o7j%u-xjTufL%kaH4}h@Cf{u1L2tJy6Qqc;K3+Jyu0-0!%Uvs<9~vHp|xn^ zD|d82I|qmcI8xJi{JzjjPxE@>B^T)%Bs^eLLUo-$aIM=#Jnlh3+~~Cxnz^J~FHEZs z>s+}i$*2*IGFEnR?!_96u3-kfn{>3Goa22H59S=^1so;yoPz*$$554x=^KJ8`aoyA zx$0`oVq@9%%6e&fW7uHKKrw>{6Y3Yb$&A{2U zH_wf+;jY3XU`S5*VX}JJ&UTXl?+fV>8wsX->yRj!$C%c+|3#FHPmd;0z;3Ls0(dv7gO{; zkWgPz1zPtu=~X8YA+RvWh;5E0htv-Ax7|j93^e z;UtD(O!n8q(~=2>zaL(J+`!n38%_sa*{y5EAn{Ze*N5M%z&(VfTpCB+kgE)MKhb`Y z=xzCEqc2fzX74P%S{3a(u=vxsb5Ig?@1@l!wB#1p+$*)zFFB3uZy^{l_KCcFbqaQh zKr^sk{8-sfNFxpgrg&2CH%LHUu>XlpxOmaU$9oq!neF7Vhw)0?K}+d#!J}H8{5N%k z7mkT)3xpFaxzW{^c{Bb$GhRV7YvQn0vgfR7S5!-5Yu3v2Y!IYLtTJl0=aUH)I2 z3vD}{69yq@*T0=*d{`Emzuis((v5OvBPM+^k6!3BcC+jgqluY;{umcllnnOZfK03v z>#vU%ByX>Psb@_Xn~@Tkeh8d8Q0g&L(?*J*lc&vR;;R>5E-GjjMP7a$Dis;jF@)M` z>Iitqv_a05Djc_Ts-9dh`{pYx_8GNmMi5r&@!PqV2gJs2<&;NhSRelgOtcN_bnVEY zdaxCE>&8LGi_gel>`wW_I&Wz2*@Fml9XJ@P&^+aM^PskClnk;jgBil>5Ok0j-CUqQ zJ%y9RFamQhyW)S26uaNaKCuoiinCe0yl#-2LB3&eKQ_G9I>^!68>IOL} z>7QiiplCx~KM5@5U^k?Z)X)w5Wv}#KB%jH|nane79qmFDA1Pr{aR3doG&{l3bvp36yRk1YGwW7PWa1z1#j{gI z_QO~=KNTeq;FVibkz>|V2<)k>2`;8+pA~q8;MpEtKTHs#aK+2JfKH_JY4-rZ&gcxN z!wX+mB}S0)t;IrzGNA*bqs~bGkAxYYd0ON!d{GH0TOQ^GuW)U*)aOx;s}Ly8((n4k zBCR_t4Goro+-cDw$6Ax3MMM_S`om(MbczYF(IYK#==`l zU)f)g4yJjHi2fzc#Qa-St_#1fTAPuXVV+*e^ri5!mrduM9yp7@3Cr;*60#oX!WoP4 zfP28FpU{l=a9u+F)uD=rK|rAQKp$9}YT!9)H_Nh@~LW>01f7 zN#&bq^Tx5PR@WJBRhgtKnC0cf6JPF_ru+I`XRnR>BUc(E%x)mB;jr{H?WBxG5cFW4 z;sU5cnGG+_E+1q8Ix?`7oLB(t#P$0A#G5`AX+&hMiDblB7}G8rgIOKN2i**ckF-T5 z-L}*M(%G3#fd(pNx+B3bcQJx*0kk6zW*Sb@&g;3#%+jFE{c(K?Udu>eVRy$CG-Ybt z95RG_AJdimfYQRir&0gNv^=ag?7Q82sZ3dfhfQWO~aHl6LKGN+Ia4N-%h3mTD z6CFk)@5uMgELKQ33T0Dh+R&N0MwXAg`E9f*sRbP#>f@1~d2YSfRt9<7`Zh|DCJj1k ztzf@i@zQbhSEF-g6e|q7q6<@Z5el!?-LYX8+1Ggg>vvCtRov;bx8#ripgTOz|3f0M zKN^gERDSk0{0T}M5QQK#oa))}^lAEubG;eJd!2|t!u~mH;1j<7*!TZ$9E2fufH5y| z{g7FbAUKGXNdJJIWgy_G^4BSqPe|n_;jK`>Ycj|P$Q(s>2>a^Vc?%E;ABc^p2DT}t zY>grU4LDx>@=qfrL8f!64^7WQwht21r!GsNX&yY=gRSc4)t1$k{GctqTN?>W1r*%2 z(MJ4+dNHS|>8;bRArE354Q@cu3f@L-p9#uPxQA5z;OWOMYsYh44QZn9qBPsEZ5<|x>Q0|5$ z{;_;R4l5cI>e~2>#7Li;sNC9RKx-WBu;MW?Wobj;jD^E7Ti%>T%3mH;rhhJ7kYMRN ziiG6W{&P`*dYjO{zg{f`0_h3%0m|t88?!zk1!BMUF#h6w6W?2sh13bU$zvsKccti9 zM9f6vWBW)wN`Bnxfd@rTD8$@{=1&GL<{^vstscFa%~dm}$%H9+z{l38-J%zoH#!$z zmoa$y?)z-bQ$%ShoPY#RaD!k32j?hcq9clP`{;a=>c{^sX;Dai6VP5&x9OY&pkZlC zy)AGfE^ICfyE2D2dK9@nuET$Ivz*^u$a{Wc$6`x7g-F; z51RERBl}^*+N-L$7xpgO&hNT##W)AD1cLl)l29gfrSQ@qF27DN$v&t1sQsO*KKu!6IrKGlT=~nZrSq_ir{5Q#^R*U@Oss$;rZAd)CVk&)TeP$|=G5<}2zcq>QEbl0b0 zj->k zzPBqM+*9&bT2vs&=rgHVQ%Fb-MG!2%uW!}WXcUi!o5fYFlHe))C^3HU&4z$=$&`05 zUHloUKn(6eGB!PLF~_3m%3ckftXK;I`=FTyz)6+6Xqw>2e(!LT}tIdG^xK^jXVg*u#zcr{M^=17;aX zs3djR7B;C#_wm6=6BH4%^WuOWy5J`Hn9mn=8}WdKhUqC8Tor`+s>^TaET2~iF6CZr zAngFH`Hxz?lnqPfFNGmkSzc=sB-BVL{C&l+@z&|}sC5hXm_y8a{^4EE+exQcV*F%| z8&UkM3}6#ia}*~c z1o_Yi8hh$5^o-P*z*X510`fp6I4e~rgwaNVjLgoLD8iFP)T^bB;+~r7?%`|Bfm(O@ z<`Lk$<~YXsE@$2R8y-DP&OT1M3)dDZG}QLP*EWeZbQ)N4vS~~);J=|Qz`d(wbktCdi2eH zU?ntBH|>_^-shzA8q@Fqpe|jV28P%L)hgn;O|N=l$g8c7OjuQGm$!~H5ai~$W(P9V zHXQaEG>P~u8>l-@OzavsCMK_f3|Y16w99r=;M_e<8(!~fx>X5*f15889vYj=EJ178 zCLsy+d5L#n3`EN`28xb2f{vZ_A3?nIqoHcZpU9E@9Fq?l*X^pNZ+!>Va)=!FKF-n% zOHF_jPrtP$AB93RjNaS}iY(-ZHi&yc7u_ha|4mfA)hs=jEO>Q2lxTI9!u#9caj0^+ zioEu8;rrOA6w=g?ARQY`?LICZh~hiKx0{$C;$;#<;jRy|PTb)wA2t!$tGvyJAMTj= znv6gM8PAEu!uHW(Cepg~+6rW^PKkP@2j6v@2phN)(x~a9$W(`1-rv^ zsh58%n-S1utB*&D3ewU=o$+A5Q%LxG?=ZT`zIqjDe|N4TPCY6$_C1PU z)In+M{NDd+fyiNq-&D5sd zI#fA0E*WJ|N=F9A`pYx7pb0oPH2Ha}%e$Z{4N>S&U`<&pRVKCb>i0Ph-{n_N<83>A zxUf)=lTZ0|neYpr6dqGbXia7yf3gIo+$luuPhxaG$xv)#O!nUFW|L0@_wpH);{!*D zU1`-qz0*9X$q(d%>fZ+3Kv8v5pcjk`Kvyv733KO$|JrW;b(@N&K~8avDtJC!>m?B$ z4C9MCC)m(?{_)`)X>rbq+YO$d`V-b3a9nfavMemdt+HII`!ElsRGAQ&fZ}iDHEPOsT;bz>7#6u}wYItla z<@Y(QjV|7wX~{<=VUe;X_-i*=82=+*hAnBAg}F{vCLbNvY-^B29+iuEz1vZPHXDGo z6y-);)&2U$?$@CN`=7~FX&$Umk>g8BYu8vHQT!wbGUQ&O7VFbbUnOQNZZaQEs=rlE zzPdayf_4Sz6^|K}g4 zv+|jyQZNh(7{py^E*4|1SHJf?#Q$=RFrpan2&J^;hzsgNf4f~M`I(q2@Hh8vaU0RI z^zA%$r;A=YjjGFoNf1qvmh9oe&?e6;lK@p~j@{4}MA!4GHj> z3TJ7=4vE72QF6nu^h|kjz3FbyajFHitDu0(+7s5lL{UhtE)MIa_by9jzSP^(@lQCvUFn~{P$^GhaT9G~n=hh!GAQTvrOLqGezFbe z*4uY!l7xrmTuD+jmUV@Byr$%#{<~8&VEu?$Z|J~c?5^VIH(ye0mAHRl+3D$Lg@pzD z>!uHZDe<5GgzJ3HRDdM%s1n*X&C(6k;W*#tKOIxnU`KP#E>K;&a8x9njt*t;%pqCO z5+2Df%vpOeLez13UGBLK-Oi(!Oq~eLSc%PPKI{)c?y;vnd)^NEA=1yw4(%_xUc|F{ zaT(PX$~@8hDnHWD@h<*yBx`B(?`Rt|Z*0zI*z}^mrlTR%D)CK2T{z+KC8gtx-R|`_ z-FKJ2$4QIlR5m}M6td|c_xk#1?r(k$_tST-_97`5VN91xXH$WfbJla;r5zKZJi$ZN?{J;%4-0coO29@QmNMtb-)?@juVHa;h>K4BttFPq;>jN0bDia`G*j13 zUVID75Q#?7v}t(`t}@s>Vz!v~ereKm8v-6GuJ^S@I`q7`?GLpjvd1P5FKZk|n|m!= zCw*~q4+ESuTwL8l9UUqii>GnMrs-49YJBmf`}vwuc=7rqsmWjRyi9GeSTd~~j1Gu> z3df?E<8|=d81oSsXP~?jC6Dws#~*6`{6RQ$*X&W^jeL$YNUC+-n1C! z`%IX&m~-T#pL1@1;v`FUs7n4aa=1o2()+B+&&2!ur*JR%ce99_pY9Crbv~6K_NF^# z;nqA?3j~WoQi&Vfr&60UcbR?RQjw^~+AY3YP_x3y$a8R%^1v-DAuK}ca)@M4B&d;B zO02fTgDliA#dBJgW=qbZvO1(IgRg+jcVx4+gp+s~e<-N;{JoECrO|Yj>p_?1rKQ_! z-zt7}wsR)lMaU_sw|T0QfKue=rjGSTO-5_7UdJ0U8s=f~k3OCbQH(oD#KwK)JIif7 zvkr>bOAenlUrY&6GC!`q@nrPcddZ)PEDhNiF6T>SuM-B1IXb+Tf#{Zhhi|6!d*a^R zynQUPv=kb4=s>7XDj#H}W zYqfp%owCuLOgo@#1R))kY!OqOViHlDJ@-|zl~iwcZGaAc29jxOe7-2yu5#nX3F9XX z5rnBX;;UTxWMO|!j_;CZoXU@Bggk%ef67fTM(Vl{-3>IgyfW>lpF9%PUJm7jca416 z^-jtD?$TWG*|ad#|JX-7Ry@L$%TMgZ>I@0-9In?qSzjI{U2ivH-DQ@hdV^IJB&dQ7 z>6?wW&!`~Md<*FZuM^7&&o3)AQL!lfa;WoJu*u3H9nRdgPK`TlVd9Y7a++qNO2KPJ z(BiB2=?)09@GB`RP%KkK11(&*FV;MsGJ_Gp;;5P~{?#_HT=tl^lJ) z_Ro95%uF?Xlv&VRHEIi=sxawOGoRZQ>mbzZ$Orp2br4g8mdG_#A zZ0XKORrw1(IjWAh$?f#w23ZT?Znv59{@p4Hxy_}E%ZXCX&4VA7M`~2r-rZ3#qplAn zXlfd2x{0s!dCDk+q0q?IifXtnm%NV?7gGP<%Js%dtVdk2OGtLgLv=S>kqwir>){5p zB?&copi?COkyT&;6S*W2e^>vXA9(`SUOXrk_96T0P!01JHNWT4m|X>BRBTy|UKxdqY3*SMC#^1oh0ULKEdyismGhKfKX z`9I>Jrmq)0DOCR4f01w_V`fEvn~hj%Ue{KayaIn6I@xpkQV424eOr&1?1 zspXN>tgSRobmN-Oo-mpO97-}3Y~;`X9q`EiHl&(A)^h*hjiYNSXT1&9(}&$+q`jjz zjkI>r-jNKEqzGe`{KVTJMT%@Zf?qU(< z=Ng?7H(x{ATlX7s>TFnlZv^ou;}lze;l4Thh%oNFv#)wzz&c&bo%u>%?ngIpx^GY1 z%@PdF@j7%JtaJV#|G{TeXIJ}E227nval+0XA*uOIwtzpETkp=@!R*Hqd)aq*NOjh> z$}_1RfNEBiNRFufSNh*Fd#QRk_c=#wjAM=dvKIaj2PhtBObu6q({XZ76q{q&bUf?7RjGkv9fCPjcbso zTXjY4lBKe|wJv9%)V3USS3&1HmxaZ)WWR^;Gw-_U-&jOe>9c8}$_u!DDgpbSlw z^omM9Rd1r#CcO+aKW6AbGY=B$G>PC$<*xAUzmwcPww z8Yh?Z*(f>s_{neD*JPHd-Jk!*%E~W+QHr*2Ka+S>Lq6goqWfqx(J~@jdxm|%`+mJ9%bwPzprO(W-*pxkuakV=q zV(l{b>wJs9nrqf8SCwx{&mZI37m9t`)5Szcg`(LJNo|)kfgiA{^@Q~e0PBWxrs?3S z+*|1WP{kR(a-IRT&K76VDEt>@oR$4T{4Cva^eL?U3LWSnq}7^_j#muWd930FE9=5Z7W zqA$9?imirZsF0a`XR|y>1*$X=$kWLvAlJr^8X9IC$~ZX45XsZ4EzQ(HC-L&&taZ?7 zr~Ks~G*$xW2gjKx@my#Lb->6C+|ajyF3;$U;4TgAy5Q-0agjXj+BX`*w-TSwg;-`; znmZEWa}XCMjK-q7%XTIhmPfpPIatb7(U&_9tZLEnekl?V2*Vu~<|fpBh(AZ@!B?LU zoTC`%#8Px<&9;Tno=HMude*?S?oa@iKf{o|_^j(*r-`PHfR~L(R^9a;!^^WCp4Vk# zf*rKh-LhLcYV{pAY65s4h`5smag%@+n>%=QR zq6V2iWGUb|Zrr!|$gx!2=^n~YpUOkebc3VsmSYlW!g3r%Ql6N*PZd42l!#PZ3DkQS zLaS~T$Iu^zgf%i?IE3PhB!7tMA~c28mT-<9pmw}zPwh^MBiR4X?q$w(QRouIXdN;M zoR$%!m>HwDZ|f&#(bP8X6wleGhbOlZ=P0bp+5$5Nn?Z?+!n3;CzIu*CdWEf@H~qY< zpOX{b7oi>$=0vW1{;6KKMyL1d#rQ0j)8}jD%M|=1e6W`OaFewY0n-^tHKrR=HhzZH ze$eGsVY2^l6xPuR`O_AByfb7_e|s?c7BB-){IjZWUyjnYrD3$y@dL21*JKDO&THMeY%n8U2fw=LhfMk+%Q z>oF!6IBXWnMC9li{C#%%p`Twhu;SPW@Sq7A>W-MHvWBby+%OXYq@~Npk(a@_`lCH> za&uwJscDi96#=w(yok1i*$3y*dh3Hv@S_9t5@<(?EJ~X;7kis{jFc6$6eY0ycabYU zFnw6@Pqbw;26$ecDu4VlBq7M2P9!R$;&iH@fT{Q7ZzObHo)jg=1d7a~8vl;ZG4t~a zko9-M4JriHU#g6OCwZAwu?RFDISniZ4!pFMYUiz)2$ku4w|7EH&=|s(`|3tI7a7n* z7<77YJrKbzHM`l^5`znVy)-45-wQ!`ktd!cb@f9b?l4p;()yLZcn|?Gk#$gpD>(5> z!IpshZ2}gkn&@uro8Nc+`^b#1u<0uy%I)ocGy1uh(Bch3ymf|byNbK34|KDF^f@I{&!_>*-TSkepgvaB~-qg_HYgG5RgO(yuYwojFtr zo6NOs`m40vD|9#2VwG|01Ul=tJk$$W3m)XYr$RjF5A3qLTEl2*0Qg2b3xfv9MV)6T!QK%jS-w$BURzvE)^ zZz$-GJPFy}_w%}6cmt^f?+3Z?;*dnqHz#21tg9{mcsZFCNFULS6+9f0hj3db&iT~e zCSQ7Xbaq&iy7)VTrOv9;wci0Z$!gIPeRLGYdX`+^2yzEKjDEOOf!VO_*wW;)Kht$>00? zEL}AYv8u8ls$ay@|AQb~RrItI0$)|@2PCS;;7y={hADhE{LFqK4>Xb6xwWN6f@X~^ zJKZ#H^;l*pIb{^0*MNbLsiLuWQm-rJ<2RcYa~WP9Zko_7$;eX4xFHC7i_HaA>YHD{ zhc56!P{9m&j3LmDjM*hWME&|BN#TuoKm`jFFqb%|AoL$^bR<{3er+Y-AR)LaPiRU9 zDzEhRG!`snYY`uICc{HAaK%&E_Ck$;gxsMD9g!0{bQyw#8`ma%OP~v9qmMu$OprC0 z1TuDks^I;x?w*1TU@=Ik@WF=4+KPe6k;PHzm%mF8DbuWf|NoNaXvE0>*tkt2_d*U2 zDIEZ{01K7+6>W6w!^-i4sl^SoLY7sNXC;Tg=k2i(iv6D;L#}q=qYmJ(Rs?><@~?zW z2pUTUreoqpOn6lH0DP15MB9o@%T(6Xzdh6%(yN3Ysd-EWhJe^#9B^T{k-_(1URsNGx1v+wykX-Xdx*B|(Yjc!}|wGOFUFGT921GC8GezsT)|4PH9c8bZcM9DD_o(VCPh}0P_K~_5= zO@KKdR~u#q%c>A<50vjSHW;Qp1iGA!>LB^|sw-1)yS_?18uR!uE=2n#Gwr{#8=|u; zSPVCEfEi7VQlYaJKIB)69G}T_nWz_Wlneiqd4ij;q5B`BwV7f9j3rJSH^%`OcXugImD42-t}0Zq^&RWQ&rIX{hbZ6t`4)3<%>9w7UWg#UKJVG04gBEec3+~ptf zN=Ym=CV!CE1g|wKXBaM(*gT3EsX!47!vna1g1rrP-LqZYD)5;Q z=Ln-nDB6v?noF@3ueR}Nv}tH zP*FYy0N1P?AY5vDMouyaHFFJDXmI=K+wb2yn*l0B860g@5hZUSwb~RW@5}G`UbO;) zN24&eNBE&7BGmfA9ZVPmS4ajp;t)iGK)tpB=pyg{!ub^`3VggQ&^0}kxC85GWmqXj zanjofwRbS?aEi|2rtyOgS*7XIyXGSyoUa84^7_i?Qey{b_uF$yG7B4ng#~+QtO65^ zhOLmV?e$E-^?2rLTN4N^d}|hcqw%+FbT~nfesn-2MFWN z!k`}aR8DP*?b%f{uM2;)90uv9O{ZZg!{T;Wri-_n*}&=TPES>)*`FIh`~tZO<5JSz zt(=TTS+OiAh?u3$PI^IqIUB(eoK66|sMrBI56uAi!Q@KN=tMlMN*F6L!`P0I(1myK3fK%a>y3yA zxO<`P8R0umOb1n>Q zGWDIr^fx^8SzNjS3mTlbpsZ8lC8Sq~-0?dfUjWfd+1`6uvmG4Yy7e1^IF~NW{|BkV zqORWTtJ&)Yz(!m-sfuh}LuLwCshLOTIex;9Kumfef zrFN!@P(%b4LnFHjww5F(QU{wA4?G_~%j2u13q3)>R?0TegB$UghB6%a?OC^K0 z-)Oikxv_DQy~kzCKiK5sAMeg0G7fXWVSZZkq#f+ehlS`D(AnzklG7HGb zDkxw=ornXt0q%vjp*&Wnr?r=T;<3#A- z7cOvHxsCvNbsO4N=xtM#!4?7&?9i^gOV>zO@ix$NkoR1llJ2o`ISC7%J|L+#ZGLIp zPv5-hS4#hOw|;4L4EEXsp}+lnEP~4hLW_4m$O7mDl!3S^HoBBC$djC_!2k=h{lgS4$<993JstoIWlR~ z{|!Vh3397Q6veoHj9L3WL(&kFWDy>4sc#rAF`BR#n7G9t8BN07F4h;$eyHddJBbIV z+nKhw zW&{=Y&wcaJuM1K?DLtGa*yjN7svV_mG}i>tQAHwoK(o!vH{zl008M&HG;Z6vsJOVu znG|r)aM>(RyfzZ~rF^mzSpv8^@{o9VP3Y+g(mhguF4d7!?Z?|x4tOlE<~d2 z;?Hn6QGq_9WLDuKaq-ah%#uQu$!nnWJ4LNBLnH(?;<7bXjM@r+WYK2Pt%Mb}NGwYr zsA(41pFeRqqN5*IerZIaCj6g2MJX=O+L9%eQyzh+97xJCrV}pw^Oyi`@05g*1`r|9 z8#54%w}6Rp0Dz3Y+w+DbQzP6^<6V{GT2vK&o}d@lduFD}m-7tQ@1PNa#Z%5h_8Y?k zeG3iIXC7Zgn8sqzc$@snz3u_W5g_cB5*NsteamW_AM!;I!!ghpUI@M!GcQGR9o$zj z;jq6N3}>x{IRPve@wa7Luk_w6SCjYCPsDYT>x)a6XOUd@&Nn==Pg`))mU*ZcP9f1{ zu!XZac5i4eV#N_;Xlka|58j8Nz@rrW*u%%ZT zR-uae4PftU5N~nx@bXH>F3Vgdcbp?zs_jEc3C>PN zmitGn;HUEB&t|F(Qgt(3Q<)K|wVN@&=`bX7Y>U&gJ4*po`p2JxwU77eQmG%I?fjoL zCRorv2*5_!`k=M6N0WNvg@x96>wh@<`FS-KN>Egy_K_wOL*q_?4_B@JZ&x@9L(%b4kXbmPwf z1=F>wyd}%+60EA4E=IUl%@eVHBTsl73uy({PV?&?m(59wz%1>dNiLPs%ae5MwnYrI zh?YL^E3j!x*{BbqeKy~Lwg)yA>n5UBhEh`7nLi_uC>~2>6(f6-D(2d#%yup*o2B3_ zpR0itQnh8+1J}H}Mx$%6-DBs5|1E41!_vN6pw`I)tlQ*0;b^H3aMLXmPcL~wbl$|x z=0l&bJv&+;?u-o-IB4F-o1o?G90M?X-tu6DTW!WH)&|)i>MwT=Nys^1>tTPSLA0GN zdMvm6gV;%HfqUbg+~--VxVpAs_x{N+SfDV;#d#)>-apQ5u0WTSOcuVTAs=VAxC0h) zF~baISTVObO=d52i;ZRgsXh^wOwEo=1Cd~|#v0FWO*>u^HN4VHEk3###23r^1<4jw z;5V5Q$Fs_X%r8h;%8Z^e3ELLP^ti$vi>K`fXH$_pv32v0sS7c*Yp2oxuzt&5&G_=R z?hKY?S=&*J6%aEQF~^Dm7$ooESx>b|Jh4&1i&TanX&b>Mhkgc&-sn;axYrTQvC>hj z$U((tlb^U(RSM9}M87VuwQ{LRX<%j5j$$?iXjOh79`QWSY}jwm95(_XNZrkdpEuAQ zB1a)}Q75OC_c72W3^0a%VBN~q>3S1^yHwK|lVXltk*l3^ChwCVZ-yr`K0?ByCq4Xo zA0sg`XE?ZsV~Q>ZNwC721qJKmjrUUx*NS7K5b;zZ;mVpLL^)LWqI}65T9q})TEoh% ze!TAmL3@Et&KC=z9R+iyg{33K_v@!RQ;JAv*sOHR9FC$odw;CriAL5Z!`NgO1OqyK z(j!(bTUW7eaO(AB6+rb8yqT|5XZ|#I*uIuDX*wbRPXGFp_#?-i*N(oni*gR?z=)5| zBpK?D_d)N_+Va+HX)grZiaP>r53+HJiQ$G{XHa@Zsy72Yk*A-=!U*>TtFn?uCGGvb z;bKfxYCI4OIE{2dstU9a)#){Zfj+#|OUR;0ge}x$Y3QDn1U-j7&eg{r+G!m)9jB%! zrgZz@Z9;h&!&c2fu0nIt9Mx=Y!~LQ4GN5@HRYc?x z+FnmBUEXH;fmDE3Ys$Ruqg!oQ6Jns%D9tA^zbO1y8z#W`X$?UIZx;-C-67hNs$dWi zVc6+kRXQ?6NGx+;on4Jod~L0nc-;PiGuJfTKZD2T##M#JuW3%jsTZ0S``#T7fxkzu zt#n1+nLMip?5i3adG9yPZrc219q6#om&A#n`H9V)Q!XmiqEB}{Phg;zkB=+^pD@))Xp5Sd<{XU zNMxMetkUh)yk0@KN1J03xhp^`!|(fSF2JvlZ3Dtyq51v z18R}=VQhxIg!mmLgYgWh>=N}wg@uDs)yIZwzBv{9LUiBv$sV83*W8?c(WhS~Z}zti zIY$}v!{TSUE^84F!{7Cm_%f1x()G`G4mw&o5E-)C*rV{?6F!#%Q1{zH9?Uy<9VlvMjXb4sHtahJMGH5yo@g6W<56* zHhRv}0u}wY{_L#;$K@uDGi(%rP7@Eu6&Uf8V>TlmMhLLvaw$2&^z(;h|-ukoJilZabaE@%xQ|1U2klssXfQGnjcCF05(NNqmEb!1mnEi>18N zH#-nISE(MJK0Px1p#c65iS>OsRz?BAdNWTWqUK$x;canv0pEy;wpnM8+T#;VNBdeV zikiLMJ{yW55$;+hZfMijj5cNup@lYR@70e0=A0{wsrUK&wsX~$W1#OA?m`(w3aNFe z*HszL--MIp`9$i}jmV>K&bK^4NQ#-UH>ygRT({%AKs%|hJfZ5mUqJ#Sga!_Gm95so ziM7++C;y~ngMjJxi|vQwqzOF<6UREF+$A6H9_wZkHT)J$Vo<++pNGe-`ja|Ft_B(h zbUbP*sc)UISz^&(Ob3%8XGo<(N7Y;}LW}9^e@Z{pcilCNZ`n-ZGv2WUQayQY z+8oLHERe{_E516fIe*jo;X_vi^OwSf{j!2rjvYrtKZFs|jnRyYky8qXwpl?Z=2x27 z9_e!tf&wA>$|~rHX(HC`V za5V(Fa-lsYq;prJaNR=KCdu!Yry*8n@Ih9@%b2}{-_YbH<}NqMhiy>Nqn6DPa%}^= z=C8z`@?P)DKPX@y!T;kk^V2Bi-Xc~zEYbQ(MQgM7CR;3*9FuHWX-SDifA$&mZW`g0 zuDf^zLk5q35T}G5B1eTvkW}_!$Vq8AS?RiOq8j!JugewBKzrY&fZvbkZ*{=TH&gV9 zrs(bA?v5I!9UOy0>?p~pUe_eV$LvgH0wIYtCsU!P0W0tvsEa1NB!&gER{$yc-ktnj z#YYpvP8S6%KS|{dfJ7!9w~3=$RRr~FAq3+CV%NqwIApLn_m}P6zu5*t4g7*}$;p>I zW)s%G$Xv9^=$lN7se~#~>1G<^>lHQZ^&J$y1SX#sEUQn&xq>VF!rf}>R~o-MD{~0k zcxmYlwu%xGv132thRlbtO&6TpHaT4n zg#^5R$Nph)jBq#(4*!f6HIjLGt=ho-MIEK{(6*e^X1K_XR1#=fyPbo`ip3J-egh{PG7Sb(yn-*^710X=-i0x!Sbk2>ec}AL(Ghs@F8FzD42VB6m^eDy1kTb?pRF zR0uWH-XxE`N>jk)5*qKdLWJiH^XSOlFga89=cbg;PI7qbK<6pb$8bYK`U?)UN~u&~ z#b$2w&imHMX@lLj4s5bE4yA1n%DfMvt1Xs^N?8l2TU&8*jd$~`3mzEl!4*f=xWt(- zQd=ZP7#8C^D*QsfswTsGPCgyeHqVJ`VS~Zc%h%G!!mumLs0(L$7ogRdmc}EKmoE~H z!D?o%eo}W9F>|U&6FIq># zImA2kZ*lUfO2UwpyeG1~V~q4Qru~*oed{ojS@ZyvWQVO5CFpKT;rJb>inOI^`lxSR zoUJ&eJy{K)a^cYzU1k|qjpc%mu3AfHk}MGW6~skCD41I9Fju^FBXn|mYG65z3jmsc z6weElcR>jDt_b6SiV>~C=5y5|4TC#6^UeGMKeP`su0;K;UXU*7WJjHP5Vi6m?aafk zDF^x)jj90KD`m{(*6zIfBwcW_aM^U=*ul`mq|Ua1bW3}COfBL|FCvQkfMG7!R~c3L zoT|dsENjYs?`-UyhbMa)@EHH|+xUd+IYFGL-J^dFx&Okuuve9!ZKmj0x_|cM!860F z(FWTJG#T$oliaG0_XBNMJ|p_(Ws-|iFBg`@K-Arow@A3YeUH1vJ2$3Ek5^%#+E1h< z77Y}q>ukg}in^7lZ`efegN`dTp`*2pVKFXr;)II)NHFtbt=*+_H!cDP)WT>;;@657 zU(gBZ_Bq;M!kj{6+1TYid`Zb`z&q$?j}SM%ahpWO{8!1k8^QbzH)VBp<8Uh;6$h25 z{u#O68{=7J477a|SucG#@9G*9C^LpTCSAd^ ztiDB3Jvnc)s9#}9A8VNxfmrGv7u#VnE5iVQ2KA#ejQMlEjkk2A^+P|AoY&?W zy+UTV?=rD7v2tz*=K10C@O;{(ppvJZrG)FMMiZP1Q^A$Ltaxl{P%zGZtXzij0` zwy9^!t(Yfl(J(Cd);S@i*t598C6?X-4|T|1AKg;rPHzb}yw_1hBNI%R67z_>P#GlpkTK2geURD+Tg^wh}h`sYW zwc}0G@4CdDKULSOJK_^m{~QiG>$w{FjWCx)=G139n=^~QeVTmjcj?o+n27^;2gfV^ zEZb&GY>L%sgCfJFIHk<@w@B(%W^*^p_az9dRynLZvcsJ72luq2$>SoH$K$C=y|Bij z4#$_xyrM6`2H{t<+8WFV90wIYjn%r>)>=5a zeLPwgos;`ezKTF}hEFY=puIr&|u7VlwXwZ22XPC*`$^fn}I2{R{}SzTV*{{nA!5}>{IOW z(ed_bTlQ-GwN7#=1uq-4Ko>NhUw|tysraYD+O8>=qCRN3TkASw`>HGJSz*M?c*`Ez(gGtFi)27&wH3R}~BflZ3%EKq}+^^uPaWni0;_&dA)< zX_N&93k2szfH&C6{_eq!_g@Egexd$8Fx!iP7kng*{5^v&O4m2DIp>K76U z`>j*S^pnj F{sR(IP*wl{ literal 0 HcmV?d00001 From 25aa5094a4143a31f22facadde6195497b5be820 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Sat, 11 Nov 2017 22:32:00 +0100 Subject: [PATCH 06/14] alarm for failed bolus --- app/src/main/AndroidManifest.xml | 3 + .../plugins/Overview/Dialogs/ErrorDialog.java | 94 +++++++++++++++++++ .../Overview/Dialogs/ErrorHelperActivity.java | 21 +++++ .../Overview/Dialogs/NewTreatmentDialog.java | 52 ++++------ .../main/res/layout/overview_error_dialog.xml | 24 +++++ 5 files changed, 161 insertions(+), 33 deletions(-) create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/ErrorDialog.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/ErrorHelperActivity.java create mode 100644 app/src/main/res/layout/overview_error_dialog.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index be3c0e5d80..f2338f044e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -42,6 +42,9 @@ + diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/ErrorDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/ErrorDialog.java new file mode 100644 index 0000000000..f11283c029 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/ErrorDialog.java @@ -0,0 +1,94 @@ +package info.nightscout.androidaps.plugins.Overview.Dialogs; + + +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.Services.AlarmSoundService; + +public class ErrorDialog extends DialogFragment implements View.OnClickListener { + private static Logger log = LoggerFactory.getLogger(ErrorDialog.class); + Button okButton; + TextView statusView; + ErrorHelperActivity helperActivity; + + static String status; + static String title; + static int soundId; + + public ErrorDialog() { + super(); + } + + public void setStatus(String status) { + this.status = status; + } + + public void setTitle(String title) { + this.title = title; + } + + public void setSound(int soundId) { + this.soundId = soundId; + } + + public void setHelperActivity(ErrorHelperActivity activity) { + this.helperActivity = activity; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + getDialog().setTitle(title); + View view = inflater.inflate(R.layout.overview_error_dialog, container, false); + okButton = (Button) view.findViewById(R.id.overview_error_ok); + statusView = (TextView) view.findViewById(R.id.overview_error_status); + okButton.setOnClickListener(this); + setCancelable(false); + + Intent alarm = new Intent(MainApp.instance().getApplicationContext(), AlarmSoundService.class); + alarm.putExtra("soundid", soundId); + MainApp.instance().startService(alarm); + return view; + } + + @Override + public void onResume() { + super.onResume(); + if (getDialog() != null) + getDialog().getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + statusView.setText(status); + } + + @Override + public void dismiss() { + super.dismiss(); + if (helperActivity != null) { + helperActivity.finish(); + } + Intent alarm = new Intent(MainApp.instance().getApplicationContext(), AlarmSoundService.class); + MainApp.instance().stopService(alarm); + } + + @Override + public void onClick(View view) { + switch (view.getId()) { + case R.id.overview_error_ok: + log.debug("Error dialog ok button pressed"); + dismiss(); + break; + } + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/ErrorHelperActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/ErrorHelperActivity.java new file mode 100644 index 0000000000..259c9c76eb --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/ErrorHelperActivity.java @@ -0,0 +1,21 @@ +package info.nightscout.androidaps.plugins.Overview.Dialogs; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; + +public class ErrorHelperActivity extends AppCompatActivity { + public ErrorHelperActivity() { + super(); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ErrorDialog errorDialog = new ErrorDialog(); + errorDialog.setHelperActivity(this); + errorDialog.setStatus(getIntent().getStringExtra("status")); + errorDialog.setSound(getIntent().getIntExtra("soundid", 0)); + errorDialog.setTitle(getIntent().getStringExtra("title")); + errorDialog.show(this.getSupportFragmentManager(), "Error"); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/NewTreatmentDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/NewTreatmentDialog.java index dec763beff..e764b2f7bd 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/NewTreatmentDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/NewTreatmentDialog.java @@ -2,9 +2,8 @@ package info.nightscout.androidaps.plugins.Overview.Dialogs; import android.content.Context; import android.content.DialogInterface; +import android.content.Intent; import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; import android.support.v4.app.DialogFragment; import android.support.v7.app.AlertDialog; import android.text.Editable; @@ -30,12 +29,11 @@ import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.DetailedBolusInfo; -import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.db.CareportalEvent; import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.interfaces.PumpInterface; -import info.nightscout.androidaps.plugins.Overview.Notification; -import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.queue.Callback; import info.nightscout.utils.NumberPicker; import info.nightscout.utils.SafeParse; import info.nightscout.utils.ToastUtils; @@ -49,12 +47,7 @@ public class NewTreatmentDialog extends DialogFragment implements OnClickListene private Integer maxCarbs; private Double maxInsulin; - private Handler mHandler; - public NewTreatmentDialog() { - HandlerThread mHandlerThread = new HandlerThread(NewTreatmentDialog.class.getSimpleName()); - mHandlerThread.start(); - this.mHandler = new Handler(mHandlerThread.getLooper()); } final private TextWatcher textWatcher = new TextWatcher() { @@ -139,32 +132,25 @@ public class NewTreatmentDialog extends DialogFragment implements OnClickListene builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { if (finalInsulinAfterConstraints > 0 || finalCarbsAfterConstraints > 0) { - final PumpInterface pump = MainApp.getConfigBuilder(); - mHandler.post(new Runnable() { + DetailedBolusInfo detailedBolusInfo = new DetailedBolusInfo(); + if (finalInsulinAfterConstraints == 0) + detailedBolusInfo.eventType = CareportalEvent.CARBCORRECTION; + if (finalCarbsAfterConstraints == 0) + detailedBolusInfo.eventType = CareportalEvent.CORRECTIONBOLUS; + detailedBolusInfo.insulin = finalInsulinAfterConstraints; + detailedBolusInfo.carbs = finalCarbsAfterConstraints; + detailedBolusInfo.context = context; + detailedBolusInfo.source = Source.USER; + ConfigBuilderPlugin.getCommandQueue().bolus(detailedBolusInfo, new Callback() { @Override public void run() { - DetailedBolusInfo detailedBolusInfo = new DetailedBolusInfo(); - if (finalInsulinAfterConstraints == 0) - detailedBolusInfo.eventType = CareportalEvent.CARBCORRECTION; - if (finalCarbsAfterConstraints == 0) - detailedBolusInfo.eventType = CareportalEvent.CORRECTIONBOLUS; - detailedBolusInfo.insulin = finalInsulinAfterConstraints; - detailedBolusInfo.carbs = finalCarbsAfterConstraints; - detailedBolusInfo.context = context; - detailedBolusInfo.source = Source.USER; - PumpEnactResult result = pump.deliverTreatment(detailedBolusInfo); if (!result.success) { - try { - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(MainApp.sResources.getString(R.string.treatmentdeliveryerror)); - builder.setMessage(result.comment); - builder.setPositiveButton(MainApp.sResources.getString(R.string.ok), null); - builder.show(); - } catch (WindowManager.BadTokenException | NullPointerException e) { - // window has been destroyed - Notification notification = new Notification(Notification.BOLUS_DELIVERY_ERROR, MainApp.sResources.getString(R.string.treatmentdeliveryerror), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); - } + Intent i = new Intent(MainApp.instance(), ErrorHelperActivity.class); + i.putExtra("soundid", R.raw.boluserror); + i.putExtra("status", result.comment); + i.putExtra("title", MainApp.sResources.getString(R.string.treatmentdeliveryerror)); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + MainApp.instance().startActivity(i); } } }); diff --git a/app/src/main/res/layout/overview_error_dialog.xml b/app/src/main/res/layout/overview_error_dialog.xml new file mode 100644 index 0000000000..e32007ad68 --- /dev/null +++ b/app/src/main/res/layout/overview_error_dialog.xml @@ -0,0 +1,24 @@ + + + + +