diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index caf37d462d..c2724241a1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -121,6 +121,9 @@
+
+
+
= profile.carbsReqThreshold && minutesAboveThreshold <= 45 ) {
rT.carbsReq = carbsReq;
+ rT.carbsReqWithin = minutesAboveThreshold;
rT.reason += carbsReq + " add'l carbs req w/in " + minutesAboveThreshold + "m; ";
}
+
// don't low glucose suspend if IOB is already super negative and BG is rising faster than predicted
if (bg < threshold && iob_data.iob < -profile.current_basal*20/60 && minDelta > 0 && minDelta > expectedDelta) {
rT.reason += "IOB "+iob_data.iob+" < " + round(-profile.current_basal*20/60,2);
diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ReceiversModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ReceiversModule.kt
index 0921ed7cd0..5faee82d58 100644
--- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ReceiversModule.kt
+++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ReceiversModule.kt
@@ -4,8 +4,10 @@ import dagger.Module
import dagger.android.ContributesAndroidInjector
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkBluetoothStateReceiver
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkBroadcastReceiver
+import info.nightscout.androidaps.plugins.aps.loop.CarbSuggestionReceiver
import info.nightscout.androidaps.receivers.*
+
@Module
@Suppress("unused")
abstract class ReceiversModule {
@@ -18,6 +20,6 @@ abstract class ReceiversModule {
@ContributesAndroidInjector abstract fun contributesRileyLinkBluetoothStateReceiver(): RileyLinkBluetoothStateReceiver
@ContributesAndroidInjector abstract fun contributesSmsReceiver(): SmsReceiver
@ContributesAndroidInjector abstract fun contributesTimeDateOrTZChangeReceiver(): TimeDateOrTZChangeReceiver
-
+ @ContributesAndroidInjector abstract fun contributesCarbSuggestionReceiver(): CarbSuggestionReceiver
@ContributesAndroidInjector abstract fun contributesRileyLinkBroadcastReceiver(): RileyLinkBroadcastReceiver
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/APSResult.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/APSResult.java
index 5ca88bfac0..368279a2a6 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/APSResult.java
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/APSResult.java
@@ -63,6 +63,9 @@ public class APSResult {
public double smb = 0d; // super micro bolus in units
public long deliverAt = 0;
+ public int carbsReq = 0;
+ public int carbsReqWithin = 0;
+
public Constraint inputConstraints;
public Constraint rateConstraint;
@@ -99,6 +102,10 @@ public class APSResult {
return this;
}
+ public String getCarbsRequiredText() {
+ return String.format(resourceHelper.gs(R.string.carbsreq), carbsReq, carbsReqWithin);
+ }
+
@Override
public String toString() {
final PumpInterface pump = activePlugin.getActivePump();
@@ -122,11 +129,20 @@ public class APSResult {
if (smb != 0)
ret += ("SMB: " + DecimalFormatter.toPumpSupportedBolus(smb, activePlugin.getActivePump()) + " U\n");
+ if (isCarbsRequired()) {
+ ret += getCarbsRequiredText()+"\n";
+ }
+
// reason
ret += resourceHelper.gs(R.string.reason) + ": " + reason;
return ret;
- } else
- return resourceHelper.gs(R.string.nochangerequested);
+ }
+
+ if (isCarbsRequired()) {
+ return getCarbsRequiredText();
+ }
+
+ return resourceHelper.gs(R.string.nochangerequested);
}
public Spanned toSpanned() {
@@ -151,11 +167,20 @@ public class APSResult {
if (smb != 0)
ret += ("" + "SMB" + ": " + DecimalFormatter.toPumpSupportedBolus(smb, activePlugin.getActivePump()) + " U
");
+ if (isCarbsRequired()) {
+ ret += getCarbsRequiredText()+"
";
+ }
+
// reason
ret += "" + resourceHelper.gs(R.string.reason) + ": " + reason.replace("<", "<").replace(">", ">");
return Html.fromHtml(ret);
- } else
- return Html.fromHtml(resourceHelper.gs(R.string.nochangerequested));
+ }
+
+ if (isCarbsRequired()) {
+ return Html.fromHtml(getCarbsRequiredText());
+ }
+
+ return Html.fromHtml(resourceHelper.gs(R.string.nochangerequested));
}
public APSResult newAndClone(HasAndroidInjector injector) {
@@ -184,6 +209,8 @@ public class APSResult {
newResult.smbConstraint = smbConstraint;
newResult.percent = percent;
newResult.usePercent = usePercent;
+ newResult.carbsReq = carbsReq;
+ newResult.carbsReqWithin = carbsReqWithin;
}
@@ -298,6 +325,10 @@ public class APSResult {
return latest;
}
+ public boolean isCarbsRequired() {
+ return carbsReq > 0;
+ }
+
public boolean isChangeRequested() {
Constraint closedLoopEnabled = constraintChecker.isClosedLoopAllowed();
// closed loop mode: handle change at driver level
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/CarbSuggestionReceiver.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/CarbSuggestionReceiver.java
new file mode 100644
index 0000000000..fe0b9b3ef4
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/CarbSuggestionReceiver.java
@@ -0,0 +1,20 @@
+package info.nightscout.androidaps.plugins.aps.loop;
+
+import android.content.Context;
+import android.content.Intent;
+
+import javax.inject.Inject;
+
+import dagger.android.DaggerBroadcastReceiver;
+
+public class CarbSuggestionReceiver extends DaggerBroadcastReceiver {
+
+ @Inject LoopPlugin loopPlugin;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ super.onReceive(context, intent);
+ int duartion = intent.getIntExtra("ignoreDuration", 5);
+ loopPlugin.disableCarbSuggestions(duartion);
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.java
index 93fcef0f0b..c37c812a98 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.java
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.java
@@ -101,6 +101,8 @@ public class LoopPlugin extends PluginBase {
private boolean isSuperBolus;
private boolean isDisconnected;
+ private long carbsSuggestionsSuspendedUntil = 0;
+
public class LastRun {
public APSResult request = null;
public APSResult constraintsProcessed = null;
@@ -431,6 +433,58 @@ public class LoopPlugin extends PluginBase {
Constraint closedLoopEnabled = constraintChecker.isClosedLoopAllowed();
if (closedLoopEnabled.value()) {
+
+ if (allowNotification) {
+ if (resultAfterConstraints.isCarbsRequired()
+ && resultAfterConstraints.carbsReq >= sp.getInt(R.string.key_smb_enable_carbs_suggestions_threshold, 0)
+ && carbsSuggestionsSuspendedUntil < System.currentTimeMillis()) {
+
+ Intent intentAction5m = new Intent(context, CarbSuggestionReceiver.class);
+ intentAction5m.putExtra("ignoreDuration",5);
+ PendingIntent pendingIntent5m = PendingIntent.getBroadcast(context,1, intentAction5m,PendingIntent.FLAG_UPDATE_CURRENT);
+ NotificationCompat.Action actionIgnore5m = new
+ NotificationCompat.Action(R.drawable.ic_notif_aaps, "Ignore 5m", pendingIntent5m);
+
+ Intent intentAction15m = new Intent(context, CarbSuggestionReceiver.class);
+ intentAction15m.putExtra("ignoreDuration",15);
+ PendingIntent pendingIntent15m = PendingIntent.getBroadcast(context,1, intentAction15m,PendingIntent.FLAG_UPDATE_CURRENT);
+ NotificationCompat.Action actionIgnore15m = new
+ NotificationCompat.Action(R.drawable.ic_notif_aaps, "Ignore 15m", pendingIntent15m);
+
+ Intent intentAction30m = new Intent(context, CarbSuggestionReceiver.class);
+ intentAction30m.putExtra("ignoreDuration",30);
+ PendingIntent pendingIntent30m = PendingIntent.getBroadcast(context,1, intentAction30m,PendingIntent.FLAG_UPDATE_CURRENT);
+ NotificationCompat.Action actionIgnore30m = new
+ NotificationCompat.Action(R.drawable.ic_notif_aaps, "Ignore 30m", pendingIntent30m);
+
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID);
+ builder.setSmallIcon(R.drawable.notif_icon)
+ .setContentTitle(resourceHelper.gs(R.string.carbssuggestion))
+ .setContentText(resultAfterConstraints.getCarbsRequiredText())
+ .setAutoCancel(true)
+ .setPriority(Notification.PRIORITY_MAX)
+ .setCategory(Notification.CATEGORY_ALARM)
+ .addAction(actionIgnore5m)
+ .addAction(actionIgnore15m)
+ .addAction(actionIgnore30m)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+ .setVibrate(new long[]{1000, 1000, 1000, 1000, 1000});
+
+ NotificationManager mNotificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+
+ // mId allows you to update the notification later on.
+ mNotificationManager.notify(Constants.notificationID, builder.build());
+ rxBus.send(new EventNewOpenLoopNotification());
+
+ // Send to Wear
+ actionStringHandler.get().handleInitiate("changeRequest");
+
+ } else {
+ dismissSuggestion();
+ }
+ }
+
if (resultAfterConstraints.isChangeRequested()
&& !commandQueue.bolusInQueue()
&& !commandQueue.isRunning(Command.CommandType.BOLUS)) {
@@ -489,36 +543,9 @@ public class LoopPlugin extends PluginBase {
if (sp.getBoolean("wearcontrol", false)) {
builder.setLocalOnly(true);
}
-
- // Creates an explicit intent for an Activity in your app
- Intent resultIntent = new Intent(context, MainActivity.class);
-
- // The stack builder object will contain an artificial back stack for the
- // started Activity.
- // This ensures that navigating backward from the Activity leads out of
- // your application to the Home screen.
- TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
- stackBuilder.addParentStack(MainActivity.class);
- // Adds the Intent that starts the Activity to the top of the stack
- stackBuilder.addNextIntent(resultIntent);
- PendingIntent resultPendingIntent =
- stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
- builder.setContentIntent(resultPendingIntent);
- builder.setVibrate(new long[]{1000, 1000, 1000, 1000, 1000});
- NotificationManager mNotificationManager =
- (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- // mId allows you to update the notification later on.
- mNotificationManager.notify(Constants.notificationID, builder.build());
- rxBus.send(new EventNewOpenLoopNotification());
-
- // Send to Wear
- actionStringHandler.get().handleInitiate("changeRequest");
+ presentSuggestion(builder);
} else if (allowNotification) {
- // dismiss notifications
- NotificationManager notificationManager =
- (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- notificationManager.cancel(Constants.notificationID);
- actionStringHandler.get().handleInitiate("cancelChangeRequest");
+ dismissSuggestion();
}
}
@@ -528,6 +555,45 @@ public class LoopPlugin extends PluginBase {
}
}
+ public void disableCarbSuggestions(int duartionMinutes) {
+ carbsSuggestionsSuspendedUntil = System.currentTimeMillis() + (duartionMinutes*60*1000);
+ dismissSuggestion();
+ }
+
+ private void presentSuggestion(NotificationCompat.Builder builder) {
+ // Creates an explicit intent for an Activity in your app
+ Intent resultIntent = new Intent(context, MainActivity.class);
+
+ // The stack builder object will contain an artificial back stack for the
+ // started Activity.
+ // This ensures that navigating backward from the Activity leads out of
+ // your application to the Home screen.
+ TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
+ stackBuilder.addParentStack(MainActivity.class);
+ // Adds the Intent that starts the Activity to the top of the stack
+ stackBuilder.addNextIntent(resultIntent);
+ PendingIntent resultPendingIntent =
+ stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
+ builder.setContentIntent(resultPendingIntent);
+ builder.setVibrate(new long[]{1000, 1000, 1000, 1000, 1000});
+ NotificationManager mNotificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ // mId allows you to update the notification later on.
+ mNotificationManager.notify(Constants.notificationID, builder.build());
+ rxBus.send(new EventNewOpenLoopNotification());
+
+ // Send to Wear
+ actionStringHandler.get().handleInitiate("changeRequest");
+ }
+
+ private void dismissSuggestion() {
+ // dismiss notifications
+ NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancel(Constants.notificationID);
+ actionStringHandler.get().handleInitiate("cancelChangeRequest");
+ }
+
public void acceptChangeRequest() {
Profile profile = profileFunction.getProfile();
final LoopPlugin lp = this;
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalResultSMB.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalResultSMB.java
index dae170d5d9..0c0c5e17ed 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalResultSMB.java
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalResultSMB.java
@@ -4,10 +4,11 @@ import org.json.JSONException;
import org.json.JSONObject;
import dagger.android.HasAndroidInjector;
-import info.nightscout.androidaps.logging.AAPSLogger;
import info.nightscout.androidaps.logging.LTag;
+import info.nightscout.androidaps.R;
import info.nightscout.androidaps.plugins.aps.loop.APSResult;
import info.nightscout.androidaps.utils.DateUtil;
+import info.nightscout.androidaps.utils.SP;
public class DetermineBasalResultSMB extends APSResult {
@@ -33,7 +34,11 @@ public class DetermineBasalResultSMB extends APSResult {
if (result.has("eventualBG")) eventualBG = result.getDouble("eventualBG");
if (result.has("snoozeBG")) snoozeBG = result.getDouble("snoozeBG");
//if (result.has("insulinReq")) insulinReq = result.getDouble("insulinReq");
- //if (result.has("carbsReq")) carbsReq = result.getDouble("carbsReq");
+
+ if (SP.getBoolean(R.string.key_smb_enable_carbs_suggestions, false)) {
+ if (result.has("carbsReq")) carbsReq = result.getInt("carbsReq");
+ if (result.has("carbsReqWithin")) carbsReqWithin = result.getInt("carbsReqWithin");
+ }
if (result.has("rate") && result.has("duration")) {
tempBasalRequested = true;
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt
index 962e443c66..b577b2927c 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt
@@ -742,6 +742,15 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
overview_cob?.text = cobText
val lastRun = loopPlugin.lastRun
+
+ if (Config.APS && lastRun?.constraintsProcessed != null && lastRun?.constraintsProcessed.carbsReq > 0) {
+ overview_cob_required.visibility = View.VISIBLE
+ var carbsRequiredString: String = String.format(resourceHelper.gs(R.string.overview_carbs_required), lastRun?.constraintsProcessed?.carbsReq, lastRun?.constraintsProcessed?.carbsReqWithin)
+ overview_cob_required.text = carbsRequiredString
+ } else {
+ overview_cob_required.visibility = View.GONE
+ }
+
val predictionsAvailable = if (Config.APS) lastRun?.request?.hasPredictions == true else Config.NSCLIENT
// pump status from ns
diff --git a/app/src/main/res/layout/overview_fragment.xml b/app/src/main/res/layout/overview_fragment.xml
index f122376c2f..aa9e1d5c3a 100644
--- a/app/src/main/res/layout/overview_fragment.xml
+++ b/app/src/main/res/layout/overview_fragment.xml
@@ -266,6 +266,18 @@
app:layout_constraintBottom_toBottomOf="@+id/overview_cob_label"
app:layout_constraintStart_toEndOf="@+id/overview_cob_colon" />
+
+
LGS
New suggestion available
+ Carbs Suggestion
+ Unsupported version of NSClient
Unsupported version of Nightscout
LOOP DISABLED BY CONSTRAINTS
Basal IOB
@@ -324,6 +326,7 @@
Percent
Absolute
Cancel temp basal
+ "%dg Additional Carbs Required Within %d Minutes
SMS Communicator
Waiting for result
Allowed phone numbers
@@ -774,10 +777,16 @@
SMB
use_smb
use_uam
+ key_smb_enable_carbs_suggestions
+ key_smb_enable_carbs_suggestions_threshold
Enable UAM
Enable SMB
+ Enable Carbs Suggestions
+ Minimum Carbs Required For Suggestion
+ Minimum grams of carbs to display a carbs suggestion alert. Carbs suggestions below this number will not trigger a notification.
Use Super Micro Boluses instead of temp basal for faster action
Detection of Unannounced meals
+ Enable carbs suggestions when BG is predicted to go below threshold
insulin_oref_peak
IOB Curve Peak Time
Peak Time [min]
@@ -1828,4 +1837,6 @@
statuslights_copy_ns
Copy NS settings (if exists)?
statuslights_overview_advanced
+ (20g needed in 45min)
+ (%dg needed in %dmin)
diff --git a/app/src/main/res/xml/pref_openapssmb.xml b/app/src/main/res/xml/pref_openapssmb.xml
index 859a75a82b..bee0d054d9 100644
--- a/app/src/main/res/xml/pref_openapssmb.xml
+++ b/app/src/main/res/xml/pref_openapssmb.xml
@@ -143,6 +143,12 @@
android:summary="@string/low_temptarget_lowers_sensitivity_summary"
android:title="@string/low_temptarget_lowers_sensitivity_title" />
+
+