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" /> + +