diff --git a/.gitignore b/.gitignore index 961dd40194..affd38763c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .gradle /local.properties .DS_Store +app/jacoco.exec /build /captures *.apk diff --git a/app/build.gradle b/app/build.gradle index 7f2c67e5d3..45e91a54d6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -117,7 +117,7 @@ android { targetSdkVersion 28 multiDexEnabled true versionCode 1500 - version "2.8.2.1-dev" + version "2.8.2.1-dev-a" buildConfigField "String", "VERSION", '"' + version + '"' buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"' buildConfigField "String", "REMOTE", '"' + generateGitRemote() + '"' @@ -241,9 +241,6 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - // https://github.com/DavidProdinger/Weekdays-Selector (used outdated 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1') - implementation(name: 'weekdaysselector-1.1.1', ext: 'aar') - testImplementation "junit:junit:$junit_version" testImplementation 'org.json:json:20201115' testImplementation "org.mockito:mockito-core:${mockitoVersion}" @@ -258,7 +255,7 @@ dependencies { testImplementation "org.skyscreamer:jsonassert:1.5.0" testImplementation "org.hamcrest:hamcrest-all:1.3" - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha03' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha04' androidTestImplementation "androidx.test.ext:junit:$androidx_junit" androidTestImplementation "androidx.test:rules:$androidx_rules" diff --git a/app/libs/weekdaysselector-1.1.1.aar b/app/libs/weekdaysselector-1.1.1.aar deleted file mode 100644 index 214a429e3a..0000000000 Binary files a/app/libs/weekdaysselector-1.1.1.aar and /dev/null differ diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/WizardModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/WizardModule.kt index 66c99d5e17..2c105e9ce7 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/WizardModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/WizardModule.kt @@ -13,12 +13,13 @@ abstract class WizardModule { @ContributesAndroidInjector abstract fun swBreakInjector(): SWBreak @ContributesAndroidInjector abstract fun swButtonInjector(): SWButton @ContributesAndroidInjector abstract fun swEditNumberWithUnitsInjector(): SWEditNumberWithUnits + @ContributesAndroidInjector abstract fun swEditNumberInjector(): SWEditNumber @ContributesAndroidInjector abstract fun swEditStringInjector(): SWEditString @ContributesAndroidInjector abstract fun swEditEncryptedPasswordInjector(): SWEditEncryptedPassword @ContributesAndroidInjector abstract fun swEditUrlInjector(): SWEditUrl @ContributesAndroidInjector abstract fun swFragmentInjector(): SWFragment @ContributesAndroidInjector abstract fun swHtmlLinkInjector(): SWHtmlLink - @ContributesAndroidInjector abstract fun swInfotextInjector(): SWInfotext + @ContributesAndroidInjector abstract fun swInfotextInjector(): SWInfoText @ContributesAndroidInjector abstract fun swItemInjector(): SWItem @ContributesAndroidInjector abstract fun swPluginInjector(): SWPlugin @ContributesAndroidInjector abstract fun swRadioButtonInjector(): SWRadioButton diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/ExtendedBolusDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/ExtendedBolusDialog.kt index 03f53421f8..36246c4c2c 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/ExtendedBolusDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/ExtendedBolusDialog.kt @@ -1,7 +1,6 @@ package info.nightscout.androidaps.dialogs import android.content.Context -import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -92,12 +91,7 @@ class ExtendedBolusDialog : DialogFragmentWithDate() { commandQueue.extendedBolus(insulinAfterConstraint, durationInMinutes, object : Callback() { override fun run() { if (!result.success) { - val i = Intent(ctx, ErrorHelperActivity::class.java) - i.putExtra("soundid", R.raw.boluserror) - i.putExtra("status", result.comment) - i.putExtra("title", resourceHelper.gs(R.string.treatmentdeliveryerror)) - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - ctx.startActivity(i) + ErrorHelperActivity.runAlarm(ctx, result.comment, resourceHelper.gs(R.string.treatmentdeliveryerror), info.nightscout.androidaps.dana.R.raw.boluserror) } } }) diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/FillDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/FillDialog.kt index 1d215a3b46..90ee51cf63 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/FillDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/FillDialog.kt @@ -1,7 +1,6 @@ package info.nightscout.androidaps.dialogs import android.content.Context -import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -161,12 +160,7 @@ class FillDialog : DialogFragmentWithDate() { commandQueue.bolus(detailedBolusInfo, object : Callback() { override fun run() { if (!result.success) { - val i = Intent(ctx, ErrorHelperActivity::class.java) - i.putExtra("soundid", R.raw.boluserror) - i.putExtra("status", result.comment) - i.putExtra("title", resourceHelper.gs(R.string.treatmentdeliveryerror)) - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - ctx.startActivity(i) + ErrorHelperActivity.runAlarm(ctx, result.comment, resourceHelper.gs(R.string.treatmentdeliveryerror), info.nightscout.androidaps.dana.R.raw.boluserror) } } }) diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/InsulinDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/InsulinDialog.kt index cfb84df81a..afc68a1e30 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/InsulinDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/InsulinDialog.kt @@ -1,7 +1,6 @@ package info.nightscout.androidaps.dialogs import android.content.Context -import android.content.Intent import android.os.Bundle import android.text.Editable import android.text.TextWatcher @@ -206,12 +205,7 @@ class InsulinDialog : DialogFragmentWithDate() { commandQueue.bolus(detailedBolusInfo, object : Callback() { override fun run() { if (!result.success) { - val i = Intent(ctx, ErrorHelperActivity::class.java) - i.putExtra("soundid", R.raw.boluserror) - i.putExtra("status", result.comment) - i.putExtra("title", resourceHelper.gs(R.string.treatmentdeliveryerror)) - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - ctx.startActivity(i) + ErrorHelperActivity.runAlarm(ctx, result.comment, resourceHelper.gs(R.string.treatmentdeliveryerror), info.nightscout.androidaps.dana.R.raw.boluserror) } } }) diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/LoopDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/LoopDialog.kt index 4d4378655b..484e445136 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/LoopDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/LoopDialog.kt @@ -1,7 +1,6 @@ package info.nightscout.androidaps.dialogs import android.content.Context -import android.content.Intent import android.os.Bundle import android.os.Handler import android.view.LayoutInflater @@ -14,7 +13,8 @@ import dagger.android.support.DaggerDialogFragment import info.nightscout.androidaps.R import info.nightscout.androidaps.activities.ErrorHelperActivity import info.nightscout.androidaps.databinding.DialogLoopBinding -import info.nightscout.androidaps.events.* +import info.nightscout.androidaps.events.EventPreferenceChange +import info.nightscout.androidaps.events.EventRefreshOverview import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.UserEntryLogger @@ -53,6 +53,7 @@ class LoopDialog : DaggerDialogFragment() { private var _binding: DialogLoopBinding? = null private var loopHandler = Handler() private var refreshDialog: Runnable? = null + // This property is only valid between onCreateView and // onDestroyView. private val binding get() = _binding!! @@ -293,12 +294,7 @@ class LoopDialog : DaggerDialogFragment() { commandQueue.cancelTempBasal(true, object : Callback() { override fun run() { if (!result.success) { - val i = Intent(ctx, ErrorHelperActivity::class.java) - i.putExtra("soundid", R.raw.boluserror) - i.putExtra("status", result.comment) - i.putExtra("title", resourceHelper.gs(R.string.tempbasaldeliveryerror)) - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - ctx.startActivity(i) + ErrorHelperActivity.runAlarm(ctx, result.comment, resourceHelper.gs(R.string.tempbasaldeliveryerror), info.nightscout.androidaps.dana.R.raw.boluserror) } } }) diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/TempBasalDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/TempBasalDialog.kt index 098253b035..b72625a664 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/TempBasalDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/TempBasalDialog.kt @@ -1,7 +1,6 @@ package info.nightscout.androidaps.dialogs import android.content.Context -import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -121,12 +120,7 @@ class TempBasalDialog : DialogFragmentWithDate() { val callback: Callback = object : Callback() { override fun run() { if (!result.success) { - val i = Intent(ctx, ErrorHelperActivity::class.java) - i.putExtra("soundid", R.raw.boluserror) - i.putExtra("status", result.comment) - i.putExtra("title", resourceHelper.gs(R.string.tempbasaldeliveryerror)) - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - ctx.startActivity(i) + ErrorHelperActivity.runAlarm(ctx, result.comment, resourceHelper.gs(R.string.tempbasaldeliveryerror), info.nightscout.androidaps.dana.R.raw.boluserror) } } } diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/TreatmentDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/TreatmentDialog.kt index 4ebaf3b7d2..fe45d727a2 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/TreatmentDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/TreatmentDialog.kt @@ -1,7 +1,6 @@ package info.nightscout.androidaps.dialogs import android.content.Context -import android.content.Intent import android.os.Bundle import android.text.Editable import android.text.TextWatcher @@ -142,12 +141,7 @@ class TreatmentDialog : DialogFragmentWithDate() { commandQueue.bolus(detailedBolusInfo, object : Callback() { override fun run() { if (!result.success) { - val i = Intent(ctx, ErrorHelperActivity::class.java) - i.putExtra("soundid", R.raw.boluserror) - i.putExtra("status", result.comment) - i.putExtra("title", resourceHelper.gs(R.string.treatmentdeliveryerror)) - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - ctx.startActivity(i) + ErrorHelperActivity.runAlarm(ctx, result.comment, resourceHelper.gs(R.string.treatmentdeliveryerror), info.nightscout.androidaps.dana.R.raw.boluserror) } } }) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/logger/LoggerCallback.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/logger/LoggerCallback.java deleted file mode 100644 index 2fa1816ad5..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/logger/LoggerCallback.java +++ /dev/null @@ -1,63 +0,0 @@ -package info.nightscout.androidaps.plugins.aps.logger; - -import org.mozilla.javascript.ScriptableObject; - -import javax.inject.Inject; - -import info.nightscout.androidaps.db.StaticInjector; -import info.nightscout.androidaps.logging.AAPSLogger; -import info.nightscout.androidaps.logging.LTag; - -/** - * Created by adrian on 15/10/17. - */ - - -public class LoggerCallback extends ScriptableObject { - - @Inject - AAPSLogger aapsLogger; - - private static StringBuffer errorBuffer = new StringBuffer(); - private static StringBuffer logBuffer = new StringBuffer(); - - - public LoggerCallback() { - //empty constructor needed for Rhino - errorBuffer = new StringBuffer(); - logBuffer = new StringBuffer(); - StaticInjector.Companion.getInstance().androidInjector().inject(this); - } - - @Override - public String getClassName() { - return "LoggerCallback"; - } - - public void jsConstructor() { - //empty constructor on JS site; could work as setter - } - - public void jsFunction_log(Object obj1) { - aapsLogger.debug(LTag.APS, obj1.toString().trim()); - logBuffer.append(obj1.toString()); - } - - public void jsFunction_error(Object obj1) { - aapsLogger.error(LTag.APS, obj1.toString().trim()); - errorBuffer.append(obj1.toString()); - } - - - public static String getScriptDebug() { - String ret = ""; - if (errorBuffer.length() > 0) { - ret += "e:\n" + errorBuffer.toString(); - } - if (ret.length() > 0 && logBuffer.length() > 0) ret += '\n'; - if (logBuffer.length() > 0) { - ret += "d:\n" + logBuffer.toString(); - } - return ret; - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/logger/LoggerCallback.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/logger/LoggerCallback.kt new file mode 100644 index 0000000000..c1b4f076d4 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/logger/LoggerCallback.kt @@ -0,0 +1,61 @@ +package info.nightscout.androidaps.plugins.aps.logger + +import info.nightscout.androidaps.db.StaticInjector +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import org.mozilla.javascript.ScriptableObject +import javax.inject.Inject + +@Suppress("unused", "FunctionName") +class LoggerCallback : ScriptableObject() { + + @Inject lateinit var aapsLogger: AAPSLogger + + override fun getClassName(): String = "LoggerCallback" + + fun jsConstructor() { + //empty constructor on JS site; could work as setter + } + + fun jsFunction_log(obj1: Any) { + aapsLogger.debug(LTag.APS, obj1.toString().trim { it <= ' ' }) + logBuffer.append(obj1.toString()) + } + + fun jsFunction_error(obj1: Any) { + aapsLogger.error(LTag.APS, obj1.toString().trim { it <= ' ' }) + errorBuffer.append(obj1.toString()) + } + + companion object { + + private var errorBuffer = StringBuffer() + private var logBuffer = StringBuffer() + val scriptDebug: String + get() { + var ret = "" + if (errorBuffer.isNotEmpty()) { + ret += """ + e: + $errorBuffer + """.trimIndent() + } + if (ret.isNotEmpty() && logBuffer.isNotEmpty()) ret += '\n' + if (logBuffer.isNotEmpty()) { + ret += """ + d: + $logBuffer + """.trimIndent() + } + return ret + } + } + + init { + //empty constructor needed for Rhino + errorBuffer = StringBuffer() + logBuffer = StringBuffer() + @Suppress("DEPRECATION") + StaticInjector.Companion.getInstance().androidInjector().inject(this) + } +} \ No newline at end of file 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 deleted file mode 100644 index ffb93ecc9f..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.java +++ /dev/null @@ -1,880 +0,0 @@ -package info.nightscout.androidaps.plugins.aps.loop; - -import android.annotation.SuppressLint; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.TaskStackBuilder; -import android.content.Context; -import android.content.Intent; -import android.os.SystemClock; - -import androidx.core.app.NotificationCompat; - -import org.jetbrains.annotations.Nullable; -import org.json.JSONException; -import org.json.JSONObject; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import dagger.Lazy; -import dagger.android.HasAndroidInjector; -import info.nightscout.androidaps.BuildConfig; -import info.nightscout.androidaps.Config; -import info.nightscout.androidaps.Constants; -import info.nightscout.androidaps.MainActivity; -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.activities.ErrorHelperActivity; -import info.nightscout.androidaps.data.DetailedBolusInfo; -import info.nightscout.androidaps.data.Profile; -import info.nightscout.androidaps.data.PumpEnactResult; -import info.nightscout.androidaps.database.entities.GlucoseValue; -import info.nightscout.androidaps.db.CareportalEvent; -import info.nightscout.androidaps.db.Source; -import info.nightscout.androidaps.db.TemporaryBasal; -import info.nightscout.androidaps.events.EventAcceptOpenLoopChange; -import info.nightscout.androidaps.events.EventNewBG; -import info.nightscout.androidaps.events.EventTempTargetChange; -import info.nightscout.androidaps.interfaces.APSInterface; -import info.nightscout.androidaps.interfaces.ActivePluginProvider; -import info.nightscout.androidaps.interfaces.CommandQueueProvider; -import info.nightscout.androidaps.interfaces.Constraint; -import info.nightscout.androidaps.interfaces.LoopInterface; -import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.interfaces.PluginDescription; -import info.nightscout.androidaps.interfaces.PluginType; -import info.nightscout.androidaps.interfaces.ProfileFunction; -import info.nightscout.androidaps.interfaces.PumpDescription; -import info.nightscout.androidaps.interfaces.PumpInterface; -import info.nightscout.androidaps.logging.AAPSLogger; -import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.plugins.aps.loop.events.EventLoopSetLastRunGui; -import info.nightscout.androidaps.plugins.aps.loop.events.EventLoopUpdateGui; -import info.nightscout.androidaps.plugins.aps.loop.events.EventNewOpenLoopNotification; -import info.nightscout.androidaps.plugins.bus.RxBusWrapper; -import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker; -import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; -import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; -import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; -import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; -import info.nightscout.androidaps.plugins.general.wear.ActionStringHandler; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished; -import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin; -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; -import info.nightscout.androidaps.queue.Callback; -import info.nightscout.androidaps.queue.commands.Command; -import info.nightscout.androidaps.receivers.ReceiverStatusStore; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.FabricPrivacy; -import info.nightscout.androidaps.utils.HardLimits; -import info.nightscout.androidaps.utils.T; -import info.nightscout.androidaps.utils.resources.ResourceHelper; -import info.nightscout.androidaps.utils.rx.AapsSchedulers; -import info.nightscout.androidaps.utils.sharedPreferences.SP; -import io.reactivex.disposables.CompositeDisposable; - -@Singleton -public class LoopPlugin extends PluginBase implements LoopInterface { - private final HasAndroidInjector injector; - private final SP sp; - private final RxBusWrapper rxBus; - private final AapsSchedulers aapsSchedulers; - private final ConstraintChecker constraintChecker; - private final ResourceHelper resourceHelper; - private final ProfileFunction profileFunction; - private final Context context; - private final CommandQueueProvider commandQueue; - private final ActivePluginProvider activePlugin; - private final TreatmentsPlugin treatmentsPlugin; - private final VirtualPumpPlugin virtualPumpPlugin; - private final Lazy actionStringHandler; - private final IobCobCalculatorPlugin iobCobCalculatorPlugin; - private final ReceiverStatusStore receiverStatusStore; - private final FabricPrivacy fabricPrivacy; - private final NSUpload nsUpload; - private final HardLimits hardLimits; - - private final CompositeDisposable disposable = new CompositeDisposable(); - - private static final String CHANNEL_ID = "AndroidAPS-Openloop"; - - private long lastBgTriggeredRun = 0; - - private long loopSuspendedTill; // end of manual loop suspend - private boolean isSuperBolus; - private boolean isDisconnected; - - private long carbsSuggestionsSuspendedUntil = 0; - private int prevCarbsreq = 0; - - @Nullable private LastRun lastRun = null; - - @Nullable @Override public LastRun getLastRun() { - return lastRun; - } - - @Override public void setLastRun(@Nullable LastRun lastRun) { - this.lastRun = lastRun; - } - - @Inject - public LoopPlugin( - HasAndroidInjector injector, - AAPSLogger aapsLogger, - AapsSchedulers aapsSchedulers, - RxBusWrapper rxBus, - SP sp, - Config config, - ConstraintChecker constraintChecker, - ResourceHelper resourceHelper, - ProfileFunction profileFunction, - Context context, - CommandQueueProvider commandQueue, - ActivePluginProvider activePlugin, - TreatmentsPlugin treatmentsPlugin, - VirtualPumpPlugin virtualPumpPlugin, - Lazy actionStringHandler, // TODO Adrian use RxBus instead of Lazy - IobCobCalculatorPlugin iobCobCalculatorPlugin, - ReceiverStatusStore receiverStatusStore, - FabricPrivacy fabricPrivacy, - NSUpload nsUpload, - HardLimits hardLimits - ) { - super(new PluginDescription() - .mainType(PluginType.LOOP) - .fragmentClass(LoopFragment.class.getName()) - .pluginIcon(R.drawable.ic_loop_closed_white) - .pluginName(R.string.loop) - .shortName(R.string.loop_shortname) - .preferencesId(R.xml.pref_loop) - .enableByDefault(config.getAPS()) - .description(R.string.description_loop), - aapsLogger, resourceHelper, injector - ); - this.injector = injector; - this.aapsSchedulers = aapsSchedulers; - this.sp = sp; - this.rxBus = rxBus; - this.constraintChecker = constraintChecker; - this.resourceHelper = resourceHelper; - this.profileFunction = profileFunction; - this.context = context; - this.activePlugin = activePlugin; - this.commandQueue = commandQueue; - this.treatmentsPlugin = treatmentsPlugin; - this.virtualPumpPlugin = virtualPumpPlugin; - this.actionStringHandler = actionStringHandler; - this.iobCobCalculatorPlugin = iobCobCalculatorPlugin; - this.receiverStatusStore = receiverStatusStore; - this.fabricPrivacy = fabricPrivacy; - this.nsUpload = nsUpload; - this.hardLimits = hardLimits; - - loopSuspendedTill = sp.getLong("loopSuspendedTill", 0L); - isSuperBolus = sp.getBoolean("isSuperBolus", false); - isDisconnected = sp.getBoolean("isDisconnected", false); - } - - @Override - protected void onStart() { - createNotificationChannel(); - super.onStart(); - disposable.add(rxBus - .toObservable(EventTempTargetChange.class) - .observeOn(aapsSchedulers.getIo()) - .subscribe(event -> invoke("EventTempTargetChange", true), fabricPrivacy::logException) - ); - /* - This method is triggered once autosens calculation has completed, so the LoopPlugin - has current data to work with. However, autosens calculation can be triggered by multiple - sources and currently only a new BG should trigger a loop run. Hence we return early if - the event causing the calculation is not EventNewBg. -

- */ - disposable.add(rxBus - .toObservable(EventAutosensCalculationFinished.class) - .observeOn(aapsSchedulers.getIo()) - .subscribe(event -> { - // Autosens calculation not triggered by a new BG - if (!(event.getCause() instanceof EventNewBG)) return; - - GlucoseValue glucoseValue = iobCobCalculatorPlugin.actualBg(); - // BG outdated - if (glucoseValue == null) return; - // already looped with that value - if (glucoseValue.getTimestamp() <= lastBgTriggeredRun) return; - - lastBgTriggeredRun = glucoseValue.getTimestamp(); - invoke("AutosenseCalculation for " + glucoseValue, true); - }, fabricPrivacy::logException) - ); - } - - private void createNotificationChannel() { - NotificationManager mNotificationManager = - (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - @SuppressLint("WrongConstant") NotificationChannel channel = new NotificationChannel(CHANNEL_ID, - CHANNEL_ID, - NotificationManager.IMPORTANCE_HIGH); - mNotificationManager.createNotificationChannel(channel); - } - - @Override - protected void onStop() { - disposable.clear(); - super.onStop(); - } - - @Override - public boolean specialEnableCondition() { - try { - PumpInterface pump = activePlugin.getActivePump(); - return pump.getPumpDescription().isTempBasalCapable; - } catch (Exception ignored) { - // may fail during initialization - return true; - } - } - - public void suspendTo(long endTime) { - loopSuspendedTill = endTime; - isSuperBolus = false; - isDisconnected = false; - sp.putLong("loopSuspendedTill", loopSuspendedTill); - sp.putBoolean("isSuperBolus", isSuperBolus); - sp.putBoolean("isDisconnected", isDisconnected); - } - - public void superBolusTo(long endTime) { - loopSuspendedTill = endTime; - isSuperBolus = true; - isDisconnected = false; - sp.putLong("loopSuspendedTill", loopSuspendedTill); - sp.putBoolean("isSuperBolus", isSuperBolus); - sp.putBoolean("isDisconnected", isDisconnected); - } - - private void disconnectTo(long endTime) { - loopSuspendedTill = endTime; - isSuperBolus = false; - isDisconnected = true; - sp.putLong("loopSuspendedTill", loopSuspendedTill); - sp.putBoolean("isSuperBolus", isSuperBolus); - sp.putBoolean("isDisconnected", isDisconnected); - } - - public int minutesToEndOfSuspend() { - if (loopSuspendedTill == 0) - return 0; - - long now = System.currentTimeMillis(); - long msecDiff = loopSuspendedTill - now; - - if (loopSuspendedTill <= now) { // time exceeded - suspendTo(0L); - return 0; - } - - return (int) (msecDiff / 60d / 1000d); - } - - public boolean isSuspended() { - if (loopSuspendedTill == 0) - return false; - - long now = System.currentTimeMillis(); - - if (loopSuspendedTill <= now) { // time exceeded - suspendTo(0L); - return false; - } - - return true; - } - - public boolean isLGS() { - Constraint closedLoopEnabled = constraintChecker.isClosedLoopAllowed(); - Double MaxIOBallowed = constraintChecker.getMaxIOBAllowed().value(); - String APSmode = sp.getString(R.string.key_aps_mode, "open"); - PumpInterface pump = activePlugin.getActivePump(); - boolean isLGS = false; - - if (!isSuspended() && !pump.isSuspended()) - if (closedLoopEnabled.value()) - if ((MaxIOBallowed.equals(hardLimits.getMAXIOB_LGS())) || (APSmode.equals("lgs"))) - isLGS = true; - - return isLGS; - } - - public boolean isSuperBolus() { - if (loopSuspendedTill == 0) - return false; - - long now = System.currentTimeMillis(); - - if (loopSuspendedTill <= now) { // time exceeded - suspendTo(0L); - return false; - } - - return isSuperBolus; - } - - public boolean isDisconnected() { - if (loopSuspendedTill == 0) - return false; - - long now = System.currentTimeMillis(); - - if (loopSuspendedTill <= now) { // time exceeded - suspendTo(0L); - return false; - } - return isDisconnected; - } - - public boolean treatmentTimethreshold(int duartionMinutes) { - long threshold = System.currentTimeMillis() + (duartionMinutes * 60 * 1000); - boolean bool = false; - if (treatmentsPlugin.getLastBolusTime() > threshold || treatmentsPlugin.getLastCarbTime() > threshold) - bool = true; - - return bool; - } - - public synchronized void invoke(String initiator, boolean allowNotification) { - invoke(initiator, allowNotification, false); - } - - public synchronized void invoke(String initiator, boolean allowNotification, boolean tempBasalFallback) { - try { - getAapsLogger().debug(LTag.APS, "invoke from " + initiator); - Constraint loopEnabled = constraintChecker.isLoopInvocationAllowed(); - - if (!loopEnabled.value()) { - String message = resourceHelper.gs(R.string.loopdisabled) + "\n" + loopEnabled.getReasons(getAapsLogger()); - getAapsLogger().debug(LTag.APS, message); - rxBus.send(new EventLoopSetLastRunGui(message)); - return; - } - final PumpInterface pump = activePlugin.getActivePump(); - APSResult result = null; - - if (!isEnabled(PluginType.LOOP)) - return; - - Profile profile = profileFunction.getProfile(); - - if (profile == null || !profileFunction.isProfileValid("Loop")) { - getAapsLogger().debug(LTag.APS, resourceHelper.gs(R.string.noprofileselected)); - rxBus.send(new EventLoopSetLastRunGui(resourceHelper.gs(R.string.noprofileselected))); - return; - } - - // Check if pump info is loaded - if (pump.getBaseBasalRate() < 0.01d) return; - - APSInterface usedAPS = activePlugin.getActiveAPS(); - if (((PluginBase) usedAPS).isEnabled(PluginType.APS)) { - usedAPS.invoke(initiator, tempBasalFallback); - result = usedAPS.getLastAPSResult(); - } - - // Check if we have any result - if (result == null) { - rxBus.send(new EventLoopSetLastRunGui(resourceHelper.gs(R.string.noapsselected))); - return; - } - - // Prepare for pumps using % basals - if (pump.getPumpDescription().tempBasalStyle == PumpDescription.PERCENT && allowPercentage()) { - result.setUsePercent(true); - } - result.setPercent((int) (result.getRate() / profile.getBasal() * 100)); - - // check rate for constraints - final APSResult resultAfterConstraints = result.newAndClone(injector); - resultAfterConstraints.setRateConstraint(new Constraint<>(resultAfterConstraints.getRate())); - resultAfterConstraints.setRate(constraintChecker.applyBasalConstraints(resultAfterConstraints.getRateConstraint(), profile).value()); - - resultAfterConstraints.setPercentConstraint(new Constraint<>(resultAfterConstraints.getPercent())); - resultAfterConstraints.setPercent(constraintChecker.applyBasalPercentConstraints(resultAfterConstraints.getPercentConstraint(), profile).value()); - - resultAfterConstraints.setSmbConstraint(new Constraint<>(resultAfterConstraints.getSmb())); - resultAfterConstraints.setSmb(constraintChecker.applyBolusConstraints(resultAfterConstraints.getSmbConstraint()).value()); - - // safety check for multiple SMBs - long lastBolusTime = treatmentsPlugin.getLastBolusTime(); - if (lastBolusTime != 0 && lastBolusTime + T.mins(3).msecs() > System.currentTimeMillis()) { - getAapsLogger().debug(LTag.APS, "SMB requested but still in 3 min interval"); - resultAfterConstraints.setSmb(0); - } - - if (lastRun != null && lastRun.getConstraintsProcessed() != null) { - prevCarbsreq = lastRun.getConstraintsProcessed().getCarbsReq(); - } - - if (lastRun == null) lastRun = new LastRun(); - lastRun.setRequest(result); - lastRun.setConstraintsProcessed(resultAfterConstraints); - lastRun.setLastAPSRun(DateUtil.now()); - lastRun.setSource(((PluginBase) usedAPS).getName()); - lastRun.setTbrSetByPump(null); - lastRun.setSmbSetByPump(null); - lastRun.setLastTBREnact(0); - lastRun.setLastTBRRequest(0); - lastRun.setLastSMBEnact(0); - lastRun.setLastSMBRequest(0); - - nsUpload.uploadDeviceStatus(this, iobCobCalculatorPlugin, profileFunction, activePlugin.getActivePump(), receiverStatusStore, BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION); - - if (isSuspended()) { - getAapsLogger().debug(LTag.APS, resourceHelper.gs(R.string.loopsuspended)); - rxBus.send(new EventLoopSetLastRunGui(resourceHelper.gs(R.string.loopsuspended))); - return; - } - - if (pump.isSuspended()) { - getAapsLogger().debug(LTag.APS, resourceHelper.gs(R.string.pumpsuspended)); - rxBus.send(new EventLoopSetLastRunGui(resourceHelper.gs(R.string.pumpsuspended))); - return; - } - - Constraint closedLoopEnabled = constraintChecker.isClosedLoopAllowed(); - - if (closedLoopEnabled.value()) { - if (allowNotification) { - if (resultAfterConstraints.isCarbsRequired() - && resultAfterConstraints.getCarbsReq() >= sp.getInt(R.string.key_smb_enable_carbs_suggestions_threshold, 0) - && carbsSuggestionsSuspendedUntil < System.currentTimeMillis() && !treatmentTimethreshold(-15)) { - - if (sp.getBoolean(R.string.key_enable_carbs_required_alert_local, true) && !sp.getBoolean(R.string.key_raise_notifications_as_android_notifications, true)) { - Notification carbreqlocal = new Notification(Notification.CARBS_REQUIRED, resultAfterConstraints.getCarbsRequiredText(), Notification.NORMAL); - rxBus.send(new EventNewNotification(carbreqlocal)); - } - if (sp.getBoolean(R.string.key_ns_create_announcements_from_carbs_req, false)) { - nsUpload.uploadError(resultAfterConstraints.getCarbsRequiredText()); - } - if (sp.getBoolean(R.string.key_enable_carbs_required_alert_local, true) && sp.getBoolean(R.string.key_raise_notifications_as_android_notifications, true)) { - 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, resourceHelper.gs(R.string.ignore5m, "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, resourceHelper.gs(R.string.ignore15m, "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, resourceHelper.gs(R.string.ignore30m, "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.IMPORTANCE_HIGH) - .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()); - - //only send to wear if Native notifications are turned off - if (!sp.getBoolean(R.string.key_raise_notifications_as_android_notifications, true)) { - // Send to Wear - actionStringHandler.get().handleInitiate("changeRequest"); - } - } - - } else { - //If carbs were required previously, but are no longer needed, dismiss notifications - if (prevCarbsreq > 0) { - dismissSuggestion(); - rxBus.send(new EventDismissNotification(Notification.CARBS_REQUIRED)); - } - } - } - - if (resultAfterConstraints.isChangeRequested() - && !commandQueue.bolusInQueue() - && !commandQueue.isRunning(Command.CommandType.BOLUS)) { - final PumpEnactResult waiting = new PumpEnactResult(getInjector()); - waiting.queued = true; - if (resultAfterConstraints.getTempBasalRequested()) - lastRun.setTbrSetByPump(waiting); - if (resultAfterConstraints.getBolusRequested()) - lastRun.setSmbSetByPump(waiting); - rxBus.send(new EventLoopUpdateGui()); - fabricPrivacy.logCustom("APSRequest"); - applyTBRRequest(resultAfterConstraints, profile, new Callback() { - @Override - public void run() { - if (result.enacted || result.success) { - lastRun.setTbrSetByPump(result); - lastRun.setLastTBRRequest(lastRun.getLastAPSRun()); - lastRun.setLastTBREnact(DateUtil.now()); - rxBus.send(new EventLoopUpdateGui()); - applySMBRequest(resultAfterConstraints, new Callback() { - @Override - public void run() { - // Callback is only called if a bolus was actually requested - if (result.enacted || result.success) { - lastRun.setSmbSetByPump(result); - lastRun.setLastSMBRequest(lastRun.getLastAPSRun()); - lastRun.setLastSMBEnact(DateUtil.now()); - } else { - new Thread(() -> { - SystemClock.sleep(1000); - invoke("tempBasalFallback", allowNotification, true); - }).start(); - } - rxBus.send(new EventLoopUpdateGui()); - } - }); - } else { - lastRun.setTbrSetByPump(result); - lastRun.setLastTBRRequest(lastRun.getLastAPSRun()); - } - rxBus.send(new EventLoopUpdateGui()); - } - }); - } else { - lastRun.setTbrSetByPump(null); - lastRun.setSmbSetByPump(null); - } - } else { - if (resultAfterConstraints.isChangeRequested() && allowNotification) { - NotificationCompat.Builder builder = - new NotificationCompat.Builder(context, CHANNEL_ID); - builder.setSmallIcon(R.drawable.notif_icon) - .setContentTitle(resourceHelper.gs(R.string.openloop_newsuggestion)) - .setContentText(resultAfterConstraints.toString()) - .setAutoCancel(true) - .setPriority(Notification.IMPORTANCE_HIGH) - .setCategory(Notification.CATEGORY_ALARM) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC); - if (sp.getBoolean("wearcontrol", false)) { - builder.setLocalOnly(true); - } - presentSuggestion(builder); - } else if (allowNotification) { - dismissSuggestion(); - } - } - - rxBus.send(new EventLoopUpdateGui()); - } finally { - getAapsLogger().debug(LTag.APS, "invoke end"); - } - } - - public void disableCarbSuggestions(int durationMinutes) { - carbsSuggestionsSuspendedUntil = System.currentTimeMillis() + (durationMinutes * 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; - applyTBRRequest(lastRun.getConstraintsProcessed(), profile, new Callback() { - @Override - public void run() { - if (result.enacted) { - lastRun.setTbrSetByPump(result); - lastRun.setLastTBRRequest(lastRun.getLastAPSRun()); - lastRun.setLastTBREnact(DateUtil.now()); - lastRun.setLastOpenModeAccept(DateUtil.now()); - nsUpload.uploadDeviceStatus(lp, iobCobCalculatorPlugin, profileFunction, activePlugin.getActivePump(), receiverStatusStore, BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION); - sp.incInt(R.string.key_ObjectivesmanualEnacts); - } - rxBus.send(new EventAcceptOpenLoopChange()); - } - }); - fabricPrivacy.logCustom("AcceptTemp"); - } - - /** - * expect absolute request and allow both absolute and percent response based on pump capabilities - * TODO: update pump drivers to support APS request in % - */ - - private void applyTBRRequest(APSResult request, Profile profile, Callback callback) { - - if (!request.getTempBasalRequested()) { - if (callback != null) { - callback.result(new PumpEnactResult(getInjector()).enacted(false).success(true).comment(resourceHelper.gs(R.string.nochangerequested))).run(); - } - return; - } - - PumpInterface pump = activePlugin.getActivePump(); - - if (!pump.isInitialized()) { - getAapsLogger().debug(LTag.APS, "applyAPSRequest: " + resourceHelper.gs(R.string.pumpNotInitialized)); - if (callback != null) { - callback.result(new PumpEnactResult(getInjector()).comment(resourceHelper.gs(R.string.pumpNotInitialized)).enacted(false).success(false)).run(); - } - return; - } - - if (pump.isSuspended()) { - getAapsLogger().debug(LTag.APS, "applyAPSRequest: " + resourceHelper.gs(R.string.pumpsuspended)); - if (callback != null) { - callback.result(new PumpEnactResult(getInjector()).comment(resourceHelper.gs(R.string.pumpsuspended)).enacted(false).success(false)).run(); - } - return; - } - - getAapsLogger().debug(LTag.APS, "applyAPSRequest: " + request.toString()); - - long now = System.currentTimeMillis(); - TemporaryBasal activeTemp = treatmentsPlugin.getTempBasalFromHistory(now); - if (request.getUsePercent() && allowPercentage()) { - if (request.getPercent() == 100 && request.getDuration() == 0) { - if (activeTemp != null) { - getAapsLogger().debug(LTag.APS, "applyAPSRequest: cancelTempBasal()"); - commandQueue.cancelTempBasal(false, callback); - } else { - getAapsLogger().debug(LTag.APS, "applyAPSRequest: Basal set correctly"); - if (callback != null) { - callback.result(new PumpEnactResult(getInjector()).percent(request.getPercent()).duration(0) - .enacted(false).success(true).comment(resourceHelper.gs(R.string.basal_set_correctly))).run(); - } - } - } else if (activeTemp != null - && activeTemp.getPlannedRemainingMinutes() > 5 - && request.getDuration() - activeTemp.getPlannedRemainingMinutes() < 30 - && request.getPercent() == activeTemp.percentRate) { - getAapsLogger().debug(LTag.APS, "applyAPSRequest: Temp basal set correctly"); - if (callback != null) { - callback.result(new PumpEnactResult(getInjector()).percent(request.getPercent()) - .enacted(false).success(true).duration(activeTemp.getPlannedRemainingMinutes()) - .comment(resourceHelper.gs(R.string.let_temp_basal_run))).run(); - } - } else { - getAapsLogger().debug(LTag.APS, "applyAPSRequest: tempBasalPercent()"); - commandQueue.tempBasalPercent(request.getPercent(), request.getDuration(), false, profile, callback); - } - } else { - if ((request.getRate() == 0 && request.getDuration() == 0) || Math.abs(request.getRate() - pump.getBaseBasalRate()) < pump.getPumpDescription().basalStep) { - if (activeTemp != null) { - getAapsLogger().debug(LTag.APS, "applyAPSRequest: cancelTempBasal()"); - commandQueue.cancelTempBasal(false, callback); - } else { - getAapsLogger().debug(LTag.APS, "applyAPSRequest: Basal set correctly"); - if (callback != null) { - callback.result(new PumpEnactResult(getInjector()).absolute(request.getRate()).duration(0) - .enacted(false).success(true).comment(resourceHelper.gs(R.string.basal_set_correctly))).run(); - } - } - } else if (activeTemp != null - && activeTemp.getPlannedRemainingMinutes() > 5 - && request.getDuration() - activeTemp.getPlannedRemainingMinutes() < 30 - && Math.abs(request.getRate() - activeTemp.tempBasalConvertedToAbsolute(now, profile)) < pump.getPumpDescription().basalStep) { - getAapsLogger().debug(LTag.APS, "applyAPSRequest: Temp basal set correctly"); - if (callback != null) { - callback.result(new PumpEnactResult(getInjector()).absolute(activeTemp.tempBasalConvertedToAbsolute(now, profile)) - .enacted(false).success(true).duration(activeTemp.getPlannedRemainingMinutes()) - .comment(resourceHelper.gs(R.string.let_temp_basal_run))).run(); - } - } else { - getAapsLogger().debug(LTag.APS, "applyAPSRequest: setTempBasalAbsolute()"); - commandQueue.tempBasalAbsolute(request.getRate(), request.getDuration(), false, profile, callback); - } - } - } - - private void applySMBRequest(APSResult request, Callback callback) { - if (!request.getBolusRequested()) { - return; - } - - PumpInterface pump = activePlugin.getActivePump(); - - long lastBolusTime = treatmentsPlugin.getLastBolusTime(); - if (lastBolusTime != 0 && lastBolusTime + 3 * 60 * 1000 > System.currentTimeMillis()) { - getAapsLogger().debug(LTag.APS, "SMB requested but still in 3 min interval"); - if (callback != null) { - callback.result(new PumpEnactResult(getInjector()) - .comment(resourceHelper.gs(R.string.smb_frequency_exceeded)) - .enacted(false).success(false)).run(); - } - return; - } - - if (!pump.isInitialized()) { - getAapsLogger().debug(LTag.APS, "applySMBRequest: " + resourceHelper.gs(R.string.pumpNotInitialized)); - if (callback != null) { - callback.result(new PumpEnactResult(getInjector()).comment(resourceHelper.gs(R.string.pumpNotInitialized)).enacted(false).success(false)).run(); - } - return; - } - - if (pump.isSuspended()) { - getAapsLogger().debug(LTag.APS, "applySMBRequest: " + resourceHelper.gs(R.string.pumpsuspended)); - if (callback != null) { - callback.result(new PumpEnactResult(getInjector()).comment(resourceHelper.gs(R.string.pumpsuspended)).enacted(false).success(false)).run(); - } - return; - } - - getAapsLogger().debug(LTag.APS, "applySMBRequest: " + request.toString()); - - // deliver SMB - DetailedBolusInfo detailedBolusInfo = new DetailedBolusInfo(); - detailedBolusInfo.lastKnownBolusTime = treatmentsPlugin.getLastBolusTime(); - detailedBolusInfo.eventType = CareportalEvent.CORRECTIONBOLUS; - detailedBolusInfo.insulin = request.getSmb(); - detailedBolusInfo.isSMB = true; - detailedBolusInfo.source = Source.USER; - detailedBolusInfo.deliverAt = request.getDeliverAt(); - getAapsLogger().debug(LTag.APS, "applyAPSRequest: bolus()"); - commandQueue.bolus(detailedBolusInfo, callback); - } - - private boolean allowPercentage() { - return virtualPumpPlugin.isEnabled(PluginType.PUMP); - } - - public void disconnectPump(int durationInMinutes, Profile profile) { - PumpInterface pump = activePlugin.getActivePump(); - - disconnectTo(System.currentTimeMillis() + durationInMinutes * 60 * 1000L); - - if (pump.getPumpDescription().tempBasalStyle == PumpDescription.ABSOLUTE) { - commandQueue.tempBasalAbsolute(0, durationInMinutes, true, profile, new Callback() { - @Override - public void run() { - if (!result.success) { - Intent i = new Intent(context, ErrorHelperActivity.class); - i.putExtra("soundid", R.raw.boluserror); - i.putExtra("status", result.comment); - i.putExtra("title", resourceHelper.gs(R.string.tempbasaldeliveryerror)); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(i); - } - } - }); - } else { - commandQueue.tempBasalPercent(0, durationInMinutes, true, profile, new Callback() { - @Override - public void run() { - if (!result.success) { - Intent i = new Intent(context, ErrorHelperActivity.class); - i.putExtra("soundid", R.raw.boluserror); - i.putExtra("status", result.comment); - i.putExtra("title", resourceHelper.gs(R.string.tempbasaldeliveryerror)); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(i); - } - } - }); - } - - if (pump.getPumpDescription().isExtendedBolusCapable && treatmentsPlugin.isInHistoryExtendedBoluslInProgress()) { - commandQueue.cancelExtended(new Callback() { - @Override - public void run() { - if (!result.success) { - Intent i = new Intent(context, ErrorHelperActivity.class); - i.putExtra("soundid", R.raw.boluserror); - i.putExtra("status", result.comment); - i.putExtra("title", resourceHelper.gs(R.string.extendedbolusdeliveryerror)); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(i); - } - } - }); - } - createOfflineEvent(durationInMinutes); - } - - public void suspendLoop(int durationInMinutes) { - suspendTo(System.currentTimeMillis() + durationInMinutes * 60 * 1000); - commandQueue.cancelTempBasal(true, new Callback() { - @Override - public void run() { - if (!result.success) { - Intent i = new Intent(context, ErrorHelperActivity.class); - i.putExtra("soundid", R.raw.boluserror); - i.putExtra("status", result.comment); - i.putExtra("title", resourceHelper.gs(R.string.tempbasaldeliveryerror)); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(i); - } - } - }); - createOfflineEvent(durationInMinutes); - } - - public void createOfflineEvent(int durationInMinutes) { - JSONObject data = new JSONObject(); - try { - data.put("eventType", CareportalEvent.OPENAPSOFFLINE); - data.put("duration", durationInMinutes); - } catch (JSONException e) { - getAapsLogger().error("Unhandled exception", e); - } - CareportalEvent event = new CareportalEvent(getInjector()); - event.date = DateUtil.now(); - event.source = Source.USER; - event.eventType = CareportalEvent.OPENAPSOFFLINE; - event.json = data.toString(); - MainApp.getDbHelper().createOrUpdate(event); - nsUpload.uploadOpenAPSOffline(event); - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.kt new file mode 100644 index 0000000000..91ced93606 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.kt @@ -0,0 +1,674 @@ +package info.nightscout.androidaps.plugins.aps.loop + +import android.annotation.SuppressLint +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.TaskStackBuilder +import android.content.Context +import android.content.Intent +import android.os.SystemClock +import androidx.core.app.NotificationCompat +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.* +import info.nightscout.androidaps.activities.ErrorHelperActivity +import info.nightscout.androidaps.data.DetailedBolusInfo +import info.nightscout.androidaps.data.Profile +import info.nightscout.androidaps.data.PumpEnactResult +import info.nightscout.androidaps.db.CareportalEvent +import info.nightscout.androidaps.db.Source +import info.nightscout.androidaps.events.EventAcceptOpenLoopChange +import info.nightscout.androidaps.events.EventNewBG +import info.nightscout.androidaps.events.EventTempTargetChange +import info.nightscout.androidaps.interfaces.* +import info.nightscout.androidaps.interfaces.LoopInterface.LastRun +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.aps.loop.events.EventLoopSetLastRunGui +import info.nightscout.androidaps.plugins.aps.loop.events.EventLoopUpdateGui +import info.nightscout.androidaps.plugins.aps.loop.events.EventNewOpenLoopNotification +import info.nightscout.androidaps.plugins.bus.RxBusWrapper +import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker +import info.nightscout.androidaps.plugins.general.nsclient.NSUpload +import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification +import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification +import info.nightscout.androidaps.plugins.general.overview.notifications.Notification +import info.nightscout.androidaps.plugins.general.wear.events.EventWearDoAction +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished +import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.queue.Callback +import info.nightscout.androidaps.queue.commands.Command +import info.nightscout.androidaps.receivers.ReceiverStatusStore +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.HardLimits +import info.nightscout.androidaps.utils.T +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.rx.AapsSchedulers +import info.nightscout.androidaps.utils.sharedPreferences.SP +import io.reactivex.disposables.CompositeDisposable +import org.json.JSONException +import org.json.JSONObject +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.math.abs + +@Singleton +open class LoopPlugin @Inject constructor( + injector: HasAndroidInjector, + aapsLogger: AAPSLogger?, + private val aapsSchedulers: AapsSchedulers, + private val rxBus: RxBusWrapper, + private val sp: SP, + config: Config, + private val constraintChecker: ConstraintChecker, + resourceHelper: ResourceHelper, + private val profileFunction: ProfileFunction, + private val context: Context, + private val commandQueue: CommandQueueProvider, + private val activePlugin: ActivePluginProvider, + private val treatmentsPlugin: TreatmentsPlugin, + private val virtualPumpPlugin: VirtualPumpPlugin, + private val iobCobCalculatorPlugin: IobCobCalculatorPlugin, + private val receiverStatusStore: ReceiverStatusStore, + private val fabricPrivacy: FabricPrivacy, + private val nsUpload: NSUpload, + private val hardLimits: HardLimits +) : PluginBase(PluginDescription() + .mainType(PluginType.LOOP) + .fragmentClass(LoopFragment::class.java.name) + .pluginIcon(R.drawable.ic_loop_closed_white) + .pluginName(R.string.loop) + .shortName(R.string.loop_shortname) + .preferencesId(R.xml.pref_loop) + .enableByDefault(config.APS) + .description(R.string.description_loop), + aapsLogger!!, resourceHelper, injector +), LoopInterface { + + private val disposable = CompositeDisposable() + private var lastBgTriggeredRun: Long = 0 + private var carbsSuggestionsSuspendedUntil: Long = 0 + private var prevCarbsreq = 0 + override var lastRun: LastRun? = null + override fun onStart() { + createNotificationChannel() + super.onStart() + disposable.add(rxBus + .toObservable(EventTempTargetChange::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ invoke("EventTempTargetChange", true) }, fabricPrivacy::logException) + ) + /* + This method is triggered once autosens calculation has completed, so the LoopPlugin + has current data to work with. However, autosens calculation can be triggered by multiple + sources and currently only a new BG should trigger a loop run. Hence we return early if + the event causing the calculation is not EventNewBg. +

+ */ + disposable.add(rxBus + .toObservable(EventAutosensCalculationFinished::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ event: EventAutosensCalculationFinished -> + // Autosens calculation not triggered by a new BG + if (event.cause !is EventNewBG) return@subscribe + val glucoseValue = iobCobCalculatorPlugin.actualBg() ?: return@subscribe + // BG outdated + // already looped with that value + if (glucoseValue.timestamp <= lastBgTriggeredRun) return@subscribe + lastBgTriggeredRun = glucoseValue.timestamp + invoke("AutosenseCalculation for $glucoseValue", true) + }, fabricPrivacy::logException) + ) + } + + private fun createNotificationChannel() { + val mNotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + @SuppressLint("WrongConstant") val channel = NotificationChannel(CHANNEL_ID, + CHANNEL_ID, + NotificationManager.IMPORTANCE_HIGH) + mNotificationManager.createNotificationChannel(channel) + } + + override fun onStop() { + disposable.clear() + super.onStop() + } + + override fun specialEnableCondition(): Boolean { + return try { + val pump = activePlugin.activePump + pump.pumpDescription.isTempBasalCapable + } catch (ignored: Exception) { + // may fail during initialization + true + } + } + + fun suspendTo(endTime: Long) { + sp.putLong("loopSuspendedTill", endTime) + sp.putBoolean("isSuperBolus", false) + sp.putBoolean("isDisconnected", false) + } + + fun superBolusTo(endTime: Long) { + sp.putLong("loopSuspendedTill", endTime) + sp.putBoolean("isSuperBolus", true) + sp.putBoolean("isDisconnected", false) + } + + private fun disconnectTo(endTime: Long) { + sp.putLong("loopSuspendedTill", endTime) + sp.putBoolean("isSuperBolus", false) + sp.putBoolean("isDisconnected", true) + } + + fun minutesToEndOfSuspend(): Int { + val loopSuspendedTill = sp.getLong("loopSuspendedTill", 0L) + if (loopSuspendedTill == 0L) return 0 + val now = System.currentTimeMillis() + val millisDiff = loopSuspendedTill - now + if (loopSuspendedTill <= now) { // time exceeded + suspendTo(0L) + return 0 + } + return (millisDiff / 60.0 / 1000.0).toInt() + } + + // time exceeded + val isSuspended: Boolean + get() { + val loopSuspendedTill = sp.getLong("loopSuspendedTill", 0L) + if (loopSuspendedTill == 0L) return false + val now = System.currentTimeMillis() + if (loopSuspendedTill <= now) { // time exceeded + suspendTo(0L) + return false + } + return true + } + val isLGS: Boolean + get() { + val closedLoopEnabled = constraintChecker.isClosedLoopAllowed() + val maxIobAllowed = constraintChecker.getMaxIOBAllowed().value() + val apsMode = sp.getString(R.string.key_aps_mode, "open") + val pump = activePlugin.activePump + var isLGS = false + if (!isSuspended && !pump.isSuspended) if (closedLoopEnabled.value()) if (maxIobAllowed == hardLimits.MAXIOB_LGS || apsMode == "lgs") isLGS = true + return isLGS + } + + // time exceeded + val isSuperBolus: Boolean + get() { + val loopSuspendedTill = sp.getLong("loopSuspendedTill", 0L) + if (loopSuspendedTill == 0L) return false + val now = System.currentTimeMillis() + if (loopSuspendedTill <= now) { // time exceeded + suspendTo(0L) + return false + } + return sp.getBoolean("isSuperBolus", false) + } + + // time exceeded + val isDisconnected: Boolean + get() { + val loopSuspendedTill = sp.getLong("loopSuspendedTill", 0L) + if (loopSuspendedTill == 0L) return false + val now = System.currentTimeMillis() + if (loopSuspendedTill <= now) { // time exceeded + suspendTo(0L) + return false + } + return sp.getBoolean("isDisconnected", false) + } + + @Suppress("SameParameterValue") + private fun treatmentTimeThreshold(durationMinutes: Int): Boolean { + val threshold = System.currentTimeMillis() + durationMinutes * 60 * 1000 + var bool = false + if (treatmentsPlugin.lastBolusTime > threshold || treatmentsPlugin.lastCarbTime > threshold) bool = true + return bool + } + + @Synchronized operator fun invoke(initiator: String, allowNotification: Boolean) { + invoke(initiator, allowNotification, false) + } + + @Synchronized + operator fun invoke(initiator: String, allowNotification: Boolean, tempBasalFallback: Boolean) { + try { + aapsLogger.debug(LTag.APS, "invoke from $initiator") + val loopEnabled = constraintChecker.isLoopInvocationAllowed() + if (!loopEnabled.value()) { + val message = """ + ${resourceHelper.gs(R.string.loopdisabled)} + ${loopEnabled.getReasons(aapsLogger)} + """.trimIndent() + aapsLogger.debug(LTag.APS, message) + rxBus.send(EventLoopSetLastRunGui(message)) + return + } + val pump = activePlugin.activePump + var apsResult: APSResult? = null + if (!isEnabled(PluginType.LOOP)) return + val profile = profileFunction.getProfile() + if (profile == null || !profileFunction.isProfileValid("Loop")) { + aapsLogger.debug(LTag.APS, resourceHelper.gs(R.string.noprofileselected)) + rxBus.send(EventLoopSetLastRunGui(resourceHelper.gs(R.string.noprofileselected))) + return + } + + // Check if pump info is loaded + if (pump.baseBasalRate < 0.01) return + val usedAPS = activePlugin.activeAPS + if ((usedAPS as PluginBase).isEnabled(PluginType.APS)) { + usedAPS.invoke(initiator, tempBasalFallback) + apsResult = usedAPS.lastAPSResult + } + + // Check if we have any result + if (apsResult == null) { + rxBus.send(EventLoopSetLastRunGui(resourceHelper.gs(R.string.noapsselected))) + return + } + + // Prepare for pumps using % basals + if (pump.pumpDescription.tempBasalStyle == PumpDescription.PERCENT && allowPercentage()) { + apsResult.usePercent = true + } + apsResult.percent = (apsResult.rate / profile.basal * 100).toInt() + + // check rate for constraints + val resultAfterConstraints = apsResult.newAndClone(injector) + resultAfterConstraints.rateConstraint = Constraint(resultAfterConstraints.rate) + resultAfterConstraints.rate = constraintChecker.applyBasalConstraints(resultAfterConstraints.rateConstraint!!, profile).value() + resultAfterConstraints.percentConstraint = Constraint(resultAfterConstraints.percent) + resultAfterConstraints.percent = constraintChecker.applyBasalPercentConstraints(resultAfterConstraints.percentConstraint!!, profile).value() + resultAfterConstraints.smbConstraint = Constraint(resultAfterConstraints.smb) + resultAfterConstraints.smb = constraintChecker.applyBolusConstraints(resultAfterConstraints.smbConstraint!!).value() + + // safety check for multiple SMBs + val lastBolusTime = treatmentsPlugin.lastBolusTime + if (lastBolusTime != 0L && lastBolusTime + T.mins(3).msecs() > System.currentTimeMillis()) { + aapsLogger.debug(LTag.APS, "SMB requested but still in 3 min interval") + resultAfterConstraints.smb = 0.0 + } + if (lastRun != null && lastRun!!.constraintsProcessed != null) { + prevCarbsreq = lastRun!!.constraintsProcessed!!.carbsReq + } + if (lastRun == null) lastRun = LastRun() + lastRun!!.request = apsResult + lastRun!!.constraintsProcessed = resultAfterConstraints + lastRun!!.lastAPSRun = DateUtil.now() + lastRun!!.source = (usedAPS as PluginBase).name + lastRun!!.tbrSetByPump = null + lastRun!!.smbSetByPump = null + lastRun!!.lastTBREnact = 0 + lastRun!!.lastTBRRequest = 0 + lastRun!!.lastSMBEnact = 0 + lastRun!!.lastSMBRequest = 0 + nsUpload.uploadDeviceStatus(this, iobCobCalculatorPlugin, profileFunction, activePlugin.activePump, receiverStatusStore, BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION) + if (isSuspended) { + aapsLogger.debug(LTag.APS, resourceHelper.gs(R.string.loopsuspended)) + rxBus.send(EventLoopSetLastRunGui(resourceHelper.gs(R.string.loopsuspended))) + return + } + if (pump.isSuspended) { + aapsLogger.debug(LTag.APS, resourceHelper.gs(R.string.pumpsuspended)) + rxBus.send(EventLoopSetLastRunGui(resourceHelper.gs(R.string.pumpsuspended))) + return + } + val 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() && !treatmentTimeThreshold(-15)) { + if (sp.getBoolean(R.string.key_enable_carbs_required_alert_local, true) && !sp.getBoolean(R.string.key_raise_notifications_as_android_notifications, true)) { + val carbReqLocal = Notification(Notification.CARBS_REQUIRED, resultAfterConstraints.carbsRequiredText, Notification.NORMAL) + rxBus.send(EventNewNotification(carbReqLocal)) + } + if (sp.getBoolean(R.string.key_ns_create_announcements_from_carbs_req, false)) { + nsUpload.uploadError(resultAfterConstraints.carbsRequiredText) + } + if (sp.getBoolean(R.string.key_enable_carbs_required_alert_local, true) && sp.getBoolean(R.string.key_raise_notifications_as_android_notifications, true)) { + val intentAction5m = Intent(context, CarbSuggestionReceiver::class.java) + intentAction5m.putExtra("ignoreDuration", 5) + val pendingIntent5m = PendingIntent.getBroadcast(context, 1, intentAction5m, PendingIntent.FLAG_UPDATE_CURRENT) + val actionIgnore5m = NotificationCompat.Action(R.drawable.ic_notif_aaps, resourceHelper.gs(R.string.ignore5m, "Ignore 5m"), pendingIntent5m) + val intentAction15m = Intent(context, CarbSuggestionReceiver::class.java) + intentAction15m.putExtra("ignoreDuration", 15) + val pendingIntent15m = PendingIntent.getBroadcast(context, 1, intentAction15m, PendingIntent.FLAG_UPDATE_CURRENT) + val actionIgnore15m = NotificationCompat.Action(R.drawable.ic_notif_aaps, resourceHelper.gs(R.string.ignore15m, "Ignore 15m"), pendingIntent15m) + val intentAction30m = Intent(context, CarbSuggestionReceiver::class.java) + intentAction30m.putExtra("ignoreDuration", 30) + val pendingIntent30m = PendingIntent.getBroadcast(context, 1, intentAction30m, PendingIntent.FLAG_UPDATE_CURRENT) + val actionIgnore30m = NotificationCompat.Action(R.drawable.ic_notif_aaps, resourceHelper.gs(R.string.ignore30m, "Ignore 30m"), pendingIntent30m) + val builder = NotificationCompat.Builder(context, CHANNEL_ID) + builder.setSmallIcon(R.drawable.notif_icon) + .setContentTitle(resourceHelper.gs(R.string.carbssuggestion)) + .setContentText(resultAfterConstraints.carbsRequiredText) + .setAutoCancel(true) + .setPriority(Notification.IMPORTANCE_HIGH) + .setCategory(Notification.CATEGORY_ALARM) + .addAction(actionIgnore5m) + .addAction(actionIgnore15m) + .addAction(actionIgnore30m) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setVibrate(longArrayOf(1000, 1000, 1000, 1000, 1000)) + val mNotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + // mId allows you to update the notification later on. + mNotificationManager.notify(Constants.notificationID, builder.build()) + rxBus.send(EventNewOpenLoopNotification()) + + //only send to wear if Native notifications are turned off + if (!sp.getBoolean(R.string.key_raise_notifications_as_android_notifications, true)) { + // Send to Wear + rxBus.send(EventWearDoAction("changeRequest")) + } + } + } else { + //If carbs were required previously, but are no longer needed, dismiss notifications + if (prevCarbsreq > 0) { + dismissSuggestion() + rxBus.send(EventDismissNotification(Notification.CARBS_REQUIRED)) + } + } + } + if (resultAfterConstraints.isChangeRequested + && !commandQueue.bolusInQueue() + && !commandQueue.isRunning(Command.CommandType.BOLUS)) { + val waiting = PumpEnactResult(injector) + waiting.queued = true + if (resultAfterConstraints.tempBasalRequested) lastRun!!.tbrSetByPump = waiting + if (resultAfterConstraints.bolusRequested) lastRun!!.smbSetByPump = waiting + rxBus.send(EventLoopUpdateGui()) + fabricPrivacy.logCustom("APSRequest") + applyTBRRequest(resultAfterConstraints, profile, object : Callback() { + override fun run() { + if (result.enacted || result.success) { + lastRun!!.tbrSetByPump = result + lastRun!!.lastTBRRequest = lastRun!!.lastAPSRun + lastRun!!.lastTBREnact = DateUtil.now() + rxBus.send(EventLoopUpdateGui()) + applySMBRequest(resultAfterConstraints, object : Callback() { + override fun run() { + // Callback is only called if a bolus was actually requested + if (result.enacted || result.success) { + lastRun!!.smbSetByPump = result + lastRun!!.lastSMBRequest = lastRun!!.lastAPSRun + lastRun!!.lastSMBEnact = DateUtil.now() + } else { + Thread { + SystemClock.sleep(1000) + invoke("tempBasalFallback", allowNotification, true) + }.start() + } + rxBus.send(EventLoopUpdateGui()) + } + }) + } else { + lastRun!!.tbrSetByPump = result + lastRun!!.lastTBRRequest = lastRun!!.lastAPSRun + } + rxBus.send(EventLoopUpdateGui()) + } + }) + } else { + lastRun!!.tbrSetByPump = null + lastRun!!.smbSetByPump = null + } + } else { + if (resultAfterConstraints.isChangeRequested && allowNotification) { + val builder = NotificationCompat.Builder(context, CHANNEL_ID) + builder.setSmallIcon(R.drawable.notif_icon) + .setContentTitle(resourceHelper.gs(R.string.openloop_newsuggestion)) + .setContentText(resultAfterConstraints.toString()) + .setAutoCancel(true) + .setPriority(Notification.IMPORTANCE_HIGH) + .setCategory(Notification.CATEGORY_ALARM) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + if (sp.getBoolean(R.string.key_wear_control, false)) { + builder.setLocalOnly(true) + } + presentSuggestion(builder) + } else if (allowNotification) { + dismissSuggestion() + } + } + rxBus.send(EventLoopUpdateGui()) + } finally { + aapsLogger.debug(LTag.APS, "invoke end") + } + } + + fun disableCarbSuggestions(durationMinutes: Int) { + carbsSuggestionsSuspendedUntil = System.currentTimeMillis() + durationMinutes * 60 * 1000 + dismissSuggestion() + } + + private fun presentSuggestion(builder: NotificationCompat.Builder) { + // Creates an explicit intent for an Activity in your app + val resultIntent = Intent(context, MainActivity::class.java) + + // 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. + val stackBuilder = TaskStackBuilder.create(context) + stackBuilder.addParentStack(MainActivity::class.java) + // Adds the Intent that starts the Activity to the top of the stack + stackBuilder.addNextIntent(resultIntent) + val resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT) + builder.setContentIntent(resultPendingIntent) + builder.setVibrate(longArrayOf(1000, 1000, 1000, 1000, 1000)) + val mNotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + // mId allows you to update the notification later on. + mNotificationManager.notify(Constants.notificationID, builder.build()) + rxBus.send(EventNewOpenLoopNotification()) + + // Send to Wear + rxBus.send(EventWearDoAction("changeRequest")) + } + + private fun dismissSuggestion() { + // dismiss notifications + val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.cancel(Constants.notificationID) + rxBus.send(EventWearDoAction("cancelChangeRequest")) + } + + fun acceptChangeRequest() { + val profile = profileFunction.getProfile() + val lp = this + applyTBRRequest(lastRun!!.constraintsProcessed, profile, object : Callback() { + override fun run() { + if (result.enacted) { + lastRun!!.tbrSetByPump = result + lastRun!!.lastTBRRequest = lastRun!!.lastAPSRun + lastRun!!.lastTBREnact = DateUtil.now() + lastRun!!.lastOpenModeAccept = DateUtil.now() + nsUpload.uploadDeviceStatus(lp, iobCobCalculatorPlugin, profileFunction, activePlugin.activePump, receiverStatusStore, BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION) + sp.incInt(R.string.key_ObjectivesmanualEnacts) + } + rxBus.send(EventAcceptOpenLoopChange()) + } + }) + fabricPrivacy.logCustom("AcceptTemp") + } + + /** + * expect absolute request and allow both absolute and percent response based on pump capabilities + * TODO: update pump drivers to support APS request in % + */ + private fun applyTBRRequest(request: APSResult?, profile: Profile?, callback: Callback?) { + if (!request!!.tempBasalRequested) { + callback?.result(PumpEnactResult(injector).enacted(false).success(true).comment(resourceHelper.gs(R.string.nochangerequested)))?.run() + return + } + val pump = activePlugin.activePump + if (!pump.isInitialized) { + aapsLogger.debug(LTag.APS, "applyAPSRequest: " + resourceHelper.gs(R.string.pumpNotInitialized)) + callback?.result(PumpEnactResult(injector).comment(resourceHelper.gs(R.string.pumpNotInitialized)).enacted(false).success(false))?.run() + return + } + if (pump.isSuspended) { + aapsLogger.debug(LTag.APS, "applyAPSRequest: " + resourceHelper.gs(R.string.pumpsuspended)) + callback?.result(PumpEnactResult(injector).comment(resourceHelper.gs(R.string.pumpsuspended)).enacted(false).success(false))?.run() + return + } + aapsLogger.debug(LTag.APS, "applyAPSRequest: $request") + val now = System.currentTimeMillis() + val activeTemp = treatmentsPlugin.getTempBasalFromHistory(now) + if (request.usePercent && allowPercentage()) { + if (request.percent == 100 && request.duration == 0) { + if (activeTemp != null) { + aapsLogger.debug(LTag.APS, "applyAPSRequest: cancelTempBasal()") + commandQueue.cancelTempBasal(false, callback) + } else { + aapsLogger.debug(LTag.APS, "applyAPSRequest: Basal set correctly") + callback?.result(PumpEnactResult(injector).percent(request.percent).duration(0) + .enacted(false).success(true).comment(resourceHelper.gs(R.string.basal_set_correctly)))?.run() + } + } else if (activeTemp != null && activeTemp.plannedRemainingMinutes > 5 && request.duration - activeTemp.plannedRemainingMinutes < 30 && request.percent == activeTemp.percentRate) { + aapsLogger.debug(LTag.APS, "applyAPSRequest: Temp basal set correctly") + callback?.result(PumpEnactResult(injector).percent(request.percent) + .enacted(false).success(true).duration(activeTemp.plannedRemainingMinutes) + .comment(resourceHelper.gs(R.string.let_temp_basal_run)))?.run() + } else { + aapsLogger.debug(LTag.APS, "applyAPSRequest: tempBasalPercent()") + commandQueue.tempBasalPercent(request.percent, request.duration, false, profile!!, callback) + } + } else { + if (request.rate == 0.0 && request.duration == 0 || abs(request.rate - pump.baseBasalRate) < pump.pumpDescription.basalStep) { + if (activeTemp != null) { + aapsLogger.debug(LTag.APS, "applyAPSRequest: cancelTempBasal()") + commandQueue.cancelTempBasal(false, callback) + } else { + aapsLogger.debug(LTag.APS, "applyAPSRequest: Basal set correctly") + callback?.result(PumpEnactResult(injector).absolute(request.rate).duration(0) + .enacted(false).success(true).comment(resourceHelper.gs(R.string.basal_set_correctly)))?.run() + } + } else if (activeTemp != null && activeTemp.plannedRemainingMinutes > 5 && request.duration - activeTemp.plannedRemainingMinutes < 30 && abs(request.rate - activeTemp.tempBasalConvertedToAbsolute(now, profile)) < pump.pumpDescription.basalStep) { + aapsLogger.debug(LTag.APS, "applyAPSRequest: Temp basal set correctly") + callback?.result(PumpEnactResult(injector).absolute(activeTemp.tempBasalConvertedToAbsolute(now, profile)) + .enacted(false).success(true).duration(activeTemp.plannedRemainingMinutes) + .comment(resourceHelper.gs(R.string.let_temp_basal_run)))?.run() + } else { + aapsLogger.debug(LTag.APS, "applyAPSRequest: setTempBasalAbsolute()") + commandQueue.tempBasalAbsolute(request.rate, request.duration, false, profile!!, callback) + } + } + } + + private fun applySMBRequest(request: APSResult, callback: Callback?) { + if (!request.bolusRequested) { + return + } + val pump = activePlugin.activePump + val lastBolusTime = treatmentsPlugin.lastBolusTime + if (lastBolusTime != 0L && lastBolusTime + 3 * 60 * 1000 > System.currentTimeMillis()) { + aapsLogger.debug(LTag.APS, "SMB requested but still in 3 min interval") + callback?.result(PumpEnactResult(injector) + .comment(resourceHelper.gs(R.string.smb_frequency_exceeded)) + .enacted(false).success(false))?.run() + return + } + if (!pump.isInitialized) { + aapsLogger.debug(LTag.APS, "applySMBRequest: " + resourceHelper.gs(R.string.pumpNotInitialized)) + callback?.result(PumpEnactResult(injector).comment(resourceHelper.gs(R.string.pumpNotInitialized)).enacted(false).success(false))?.run() + return + } + if (pump.isSuspended) { + aapsLogger.debug(LTag.APS, "applySMBRequest: " + resourceHelper.gs(R.string.pumpsuspended)) + callback?.result(PumpEnactResult(injector).comment(resourceHelper.gs(R.string.pumpsuspended)).enacted(false).success(false))?.run() + return + } + aapsLogger.debug(LTag.APS, "applySMBRequest: $request") + + // deliver SMB + val detailedBolusInfo = DetailedBolusInfo() + detailedBolusInfo.lastKnownBolusTime = treatmentsPlugin.lastBolusTime + detailedBolusInfo.eventType = CareportalEvent.CORRECTIONBOLUS + detailedBolusInfo.insulin = request.smb + detailedBolusInfo.isSMB = true + detailedBolusInfo.source = Source.USER + detailedBolusInfo.deliverAt = request.deliverAt + aapsLogger.debug(LTag.APS, "applyAPSRequest: bolus()") + commandQueue.bolus(detailedBolusInfo, callback) + } + + private fun allowPercentage(): Boolean { + return virtualPumpPlugin.isEnabled(PluginType.PUMP) + } + + fun disconnectPump(durationInMinutes: Int, profile: Profile?) { + val pump = activePlugin.activePump + disconnectTo(System.currentTimeMillis() + durationInMinutes * 60 * 1000L) + if (pump.pumpDescription.tempBasalStyle == PumpDescription.ABSOLUTE) { + commandQueue.tempBasalAbsolute(0.0, durationInMinutes, true, profile!!, object : Callback() { + override fun run() { + if (!result.success) { + ErrorHelperActivity.runAlarm(context, result.comment, resourceHelper.gs(R.string.tempbasaldeliveryerror), info.nightscout.androidaps.dana.R.raw.boluserror) + } + } + }) + } else { + commandQueue.tempBasalPercent(0, durationInMinutes, true, profile!!, object : Callback() { + override fun run() { + if (!result.success) { + ErrorHelperActivity.runAlarm(context, result.comment, resourceHelper.gs(R.string.tempbasaldeliveryerror), info.nightscout.androidaps.dana.R.raw.boluserror) + } + } + }) + } + if (pump.pumpDescription.isExtendedBolusCapable && treatmentsPlugin.isInHistoryExtendedBoluslInProgress) { + commandQueue.cancelExtended(object : Callback() { + override fun run() { + if (!result.success) { + ErrorHelperActivity.runAlarm(context, result.comment, resourceHelper.gs(R.string.extendedbolusdeliveryerror), info.nightscout.androidaps.dana.R.raw.boluserror) + } + } + }) + } + createOfflineEvent(durationInMinutes) + } + + fun suspendLoop(durationInMinutes: Int) { + suspendTo(System.currentTimeMillis() + durationInMinutes * 60 * 1000) + commandQueue.cancelTempBasal(true, object : Callback() { + override fun run() { + if (!result.success) { + ErrorHelperActivity.runAlarm(context, result.comment, resourceHelper.gs(R.string.tempbasaldeliveryerror), info.nightscout.androidaps.dana.R.raw.boluserror) + } + } + }) + createOfflineEvent(durationInMinutes) + } + + fun createOfflineEvent(durationInMinutes: Int) { + val data = JSONObject() + try { + data.put("eventType", CareportalEvent.OPENAPSOFFLINE) + data.put("duration", durationInMinutes) + } catch (e: JSONException) { + aapsLogger.error("Unhandled exception", e) + } + val event = CareportalEvent(injector) + event.date = DateUtil.now() + event.source = Source.USER + event.eventType = CareportalEvent.OPENAPSOFFLINE + event.json = data.toString() + MainApp.getDbHelper().createOrUpdate(event) + nsUpload.uploadOpenAPSOffline(event) + } + + companion object { + + private const val CHANNEL_ID = "AndroidAPS-OpenLoop" + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/ScriptReader.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/ScriptReader.java deleted file mode 100644 index e3a64dbece..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/ScriptReader.java +++ /dev/null @@ -1,41 +0,0 @@ -package info.nightscout.androidaps.plugins.aps.loop; - -import android.content.Context; -import android.content.res.AssetManager; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; - -public class ScriptReader { - - private final Context mContext; - - public ScriptReader(Context context) { - mContext = context; - } - - public byte[] readFile(String fileName) throws IOException { - - AssetManager assetManager = mContext.getAssets(); - InputStream is = assetManager.open(fileName); - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - - int nRead; - byte[] data = new byte[16384]; - - while ((nRead = is.read(data, 0, data.length)) != -1) { - buffer.write(data, 0, nRead); - } - - buffer.flush(); - - byte[] bytes = buffer.toByteArray(); - is.close(); - buffer.close(); - - - return bytes; - - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/ScriptReader.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/ScriptReader.kt new file mode 100644 index 0000000000..000cd55900 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/ScriptReader.kt @@ -0,0 +1,25 @@ +package info.nightscout.androidaps.plugins.aps.loop + +import android.content.Context +import java.io.ByteArrayOutputStream +import java.io.IOException + +class ScriptReader(private val context: Context) { + + @Throws(IOException::class) + fun readFile(fileName: String): ByteArray { + val assetManager = context.assets + val `is` = assetManager.open(fileName) + val buffer = ByteArrayOutputStream() + var nRead: Int + val data = ByteArray(16384) + while (`is`.read(data, 0, data.size).also { nRead = it } != -1) { + buffer.write(data, 0, nRead) + } + buffer.flush() + val bytes = buffer.toByteArray() + `is`.close() + buffer.close() + return bytes + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.java deleted file mode 100644 index d8af22b203..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.java +++ /dev/null @@ -1,300 +0,0 @@ -package info.nightscout.androidaps.plugins.aps.openAPSAMA; - -import androidx.annotation.Nullable; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.mozilla.javascript.Context; -import org.mozilla.javascript.Function; -import org.mozilla.javascript.NativeJSON; -import org.mozilla.javascript.NativeObject; -import org.mozilla.javascript.RhinoException; -import org.mozilla.javascript.Scriptable; -import org.mozilla.javascript.ScriptableObject; -import org.mozilla.javascript.Undefined; - -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.nio.charset.StandardCharsets; - -import javax.inject.Inject; - -import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansUploader; -import dagger.android.HasAndroidInjector; -import info.nightscout.androidaps.Constants; -import info.nightscout.androidaps.R; -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.logging.AAPSLogger; -import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.plugins.aps.loop.ScriptReader; -import info.nightscout.androidaps.plugins.aps.logger.LoggerCallback; -import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults; -import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker; -import info.nightscout.androidaps.interfaces.ProfileFunction; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; -import info.nightscout.androidaps.utils.sharedPreferences.SP; - -public class DetermineBasalAdapterAMAJS { - private final HasAndroidInjector injector; - @Inject AAPSLogger aapsLogger; - @Inject ConstraintChecker constraintChecker; - @Inject SP sp; - @Inject ProfileFunction profileFunction; - @Inject TreatmentsPlugin treatmentsPlugin; - @Inject OpenHumansUploader openHumansUploader; - - private final ScriptReader mScriptReader; - - private JSONObject mProfile; - private JSONObject mGlucoseStatus; - private JSONArray mIobData; - private JSONObject mMealData; - private JSONObject mCurrentTemp; - private JSONObject mAutosensData = null; - - private String storedCurrentTemp = null; - private String storedIobData = null; - private String storedGlucoseStatus = null; - private String storedProfile = null; - private String storedMeal_data = null; - private String storedAutosens_data = null; - - private String scriptDebug = ""; - - DetermineBasalAdapterAMAJS(ScriptReader scriptReader, HasAndroidInjector injector) { - injector.androidInjector().inject(this); - mScriptReader = scriptReader; - this.injector = injector; - } - - @Nullable - public DetermineBasalResultAMA invoke() { - - aapsLogger.debug(LTag.APS, ">>> Invoking detemine_basal <<<"); - aapsLogger.debug(LTag.APS, "Glucose status: " + (storedGlucoseStatus = mGlucoseStatus.toString())); - aapsLogger.debug(LTag.APS, "IOB data: " + (storedIobData = mIobData.toString())); - aapsLogger.debug(LTag.APS, "Current temp: " + (storedCurrentTemp = mCurrentTemp.toString())); - aapsLogger.debug(LTag.APS, "Profile: " + (storedProfile = mProfile.toString())); - aapsLogger.debug(LTag.APS, "Meal data: " + (storedMeal_data = mMealData.toString())); - if (mAutosensData != null) - aapsLogger.debug(LTag.APS, "Autosens data: " + (storedAutosens_data = mAutosensData.toString())); - else - aapsLogger.debug(LTag.APS, "Autosens data: " + (storedAutosens_data = "undefined")); - - - DetermineBasalResultAMA determineBasalResultAMA = null; - - Context rhino = Context.enter(); - Scriptable scope = rhino.initStandardObjects(); - // Turn off optimization to make Rhino Android compatible - rhino.setOptimizationLevel(-1); - - try { - - //register logger callback for console.log and console.error - ScriptableObject.defineClass(scope, LoggerCallback.class); - Scriptable myLogger = rhino.newObject(scope, "LoggerCallback", null); - scope.put("console2", scope, myLogger); - rhino.evaluateString(scope, readFile("OpenAPSAMA/loggerhelper.js"), "JavaScript", 0, null); - - //set module parent - rhino.evaluateString(scope, "var module = {\"parent\":Boolean(1)};", "JavaScript", 0, null); - rhino.evaluateString(scope, "var round_basal = function round_basal(basal, profile) { return basal; };", "JavaScript", 0, null); - rhino.evaluateString(scope, "require = function() {return round_basal;};", "JavaScript", 0, null); - - //generate functions "determine_basal" and "setTempBasal" - rhino.evaluateString(scope, readFile("OpenAPSAMA/determine-basal.js"), "JavaScript", 0, null); - rhino.evaluateString(scope, readFile("OpenAPSAMA/basal-set-temp.js"), "setTempBasal.js", 0, null); - Object determineBasalObj = scope.get("determine_basal", scope); - Object setTempBasalFunctionsObj = scope.get("tempBasalFunctions", scope); - - //call determine-basal - if (determineBasalObj instanceof Function && setTempBasalFunctionsObj instanceof NativeObject) { - Function determineBasalJS = (Function) determineBasalObj; - - //prepare parameters - Object[] params = new Object[]{ - makeParam(mGlucoseStatus, rhino, scope), - makeParam(mCurrentTemp, rhino, scope), - makeParamArray(mIobData, rhino, scope), - makeParam(mProfile, rhino, scope), - makeParam(mAutosensData, rhino, scope), - makeParam(mMealData, rhino, scope), - setTempBasalFunctionsObj}; - - NativeObject jsResult = (NativeObject) determineBasalJS.call(rhino, scope, scope, params); - scriptDebug = LoggerCallback.getScriptDebug(); - - // Parse the jsResult object to a JSON-String - String result = NativeJSON.stringify(rhino, scope, jsResult, null, null).toString(); - aapsLogger.debug(LTag.APS, "Result: " + result); - try { - JSONObject resultJson = new JSONObject(result); - openHumansUploader.enqueueAMAData(mProfile, mGlucoseStatus, mIobData, mMealData, mCurrentTemp, mAutosensData, resultJson); - determineBasalResultAMA = new DetermineBasalResultAMA(injector, jsResult, resultJson); - } catch (JSONException e) { - aapsLogger.error(LTag.APS, "Unhandled exception", e); - } - } else { - aapsLogger.error(LTag.APS, "Problem loading JS Functions"); - } - } catch (IOException e) { - aapsLogger.error(LTag.APS, "IOException"); - } catch (RhinoException e) { - aapsLogger.error(LTag.APS, "RhinoException: (" + e.lineNumber() + "," + e.columnNumber() + ") " + e.toString()); - } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { - aapsLogger.error(LTag.APS, e.toString()); - } finally { - Context.exit(); - } - - storedGlucoseStatus = mGlucoseStatus.toString(); - storedIobData = mIobData.toString(); - storedCurrentTemp = mCurrentTemp.toString(); - storedProfile = mProfile.toString(); - storedMeal_data = mMealData.toString(); - - return determineBasalResultAMA; - - } - - String getGlucoseStatusParam() { - return storedGlucoseStatus; - } - - String getCurrentTempParam() { - return storedCurrentTemp; - } - - String getIobDataParam() { - return storedIobData; - } - - String getProfileParam() { - return storedProfile; - } - - String getMealDataParam() { - return storedMeal_data; - } - - String getAutosensDataParam() { - return storedAutosens_data; - } - - String getScriptDebug() { - return scriptDebug; - } - - public void setData(Profile profile, - double maxIob, - double maxBasal, - double minBg, - double maxBg, - double targetBg, - double basalrate, - IobTotal[] iobArray, - GlucoseStatus glucoseStatus, - MealData mealData, - double autosensDataRatio, - boolean tempTargetSet) throws JSONException { - - mProfile = new JSONObject(); - mProfile.put("max_iob", maxIob); - mProfile.put("dia", Math.min(profile.getDia(), 3d)); - mProfile.put("type", "current"); - mProfile.put("max_daily_basal", profile.getMaxDailyBasal()); - mProfile.put("max_basal", maxBasal); - mProfile.put("min_bg", minBg); - mProfile.put("max_bg", maxBg); - mProfile.put("target_bg", targetBg); - mProfile.put("carb_ratio", profile.getIc()); - mProfile.put("sens", profile.getIsfMgdl()); - mProfile.put("max_daily_safety_multiplier", sp.getInt(R.string.key_openapsama_max_daily_safety_multiplier, 3)); - mProfile.put("current_basal_safety_multiplier", sp.getDouble(R.string.key_openapsama_current_basal_safety_multiplier, 4d)); - mProfile.put("skip_neutral_temps", true); - mProfile.put("current_basal", basalrate); - mProfile.put("temptargetSet", tempTargetSet); - mProfile.put("autosens_adjust_targets", sp.getBoolean(R.string.key_openapsama_autosens_adjusttargets, true)); - //align with max-absorption model in AMA sensitivity - if (mealData.usedMinCarbsImpact > 0) { - mProfile.put("min_5m_carbimpact", mealData.usedMinCarbsImpact); - } else { - mProfile.put("min_5m_carbimpact", sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact)); - } - - if (profileFunction.getUnits().equals(Constants.MMOL)) { - mProfile.put("out_units", "mmol/L"); - } - - long now = System.currentTimeMillis(); - TemporaryBasal tb = treatmentsPlugin.getTempBasalFromHistory(now); - - mCurrentTemp = new JSONObject(); - mCurrentTemp.put("temp", "absolute"); - mCurrentTemp.put("duration", tb != null ? tb.getPlannedRemainingMinutes() : 0); - mCurrentTemp.put("rate", tb != null ? tb.tempBasalConvertedToAbsolute(now, profile) : 0d); - - // as we have non default temps longer than 30 mintues - TemporaryBasal tempBasal = treatmentsPlugin.getTempBasalFromHistory(System.currentTimeMillis()); - if (tempBasal != null) { - mCurrentTemp.put("minutesrunning", tempBasal.getRealDuration()); - } - - mIobData = IobCobCalculatorPlugin.convertToJSONArray(iobArray); - - mGlucoseStatus = new JSONObject(); - mGlucoseStatus.put("glucose", glucoseStatus.glucose); - - if (sp.getBoolean(R.string.key_always_use_shortavg, false)) { - mGlucoseStatus.put("delta", glucoseStatus.short_avgdelta); - } else { - mGlucoseStatus.put("delta", glucoseStatus.delta); - } - mGlucoseStatus.put("short_avgdelta", glucoseStatus.short_avgdelta); - mGlucoseStatus.put("long_avgdelta", glucoseStatus.long_avgdelta); - - mMealData = new JSONObject(); - mMealData.put("carbs", mealData.carbs); - mMealData.put("boluses", mealData.boluses); - mMealData.put("mealCOB", mealData.mealCOB); - - if (constraintChecker.isAutosensModeEnabled().value()) { - mAutosensData = new JSONObject(); - mAutosensData.put("ratio", autosensDataRatio); - } else { - mAutosensData = null; - } - } - - - private Object makeParam(JSONObject jsonObject, Context rhino, Scriptable scope) { - - if (jsonObject == null) return Undefined.instance; - - Object param = NativeJSON.parse(rhino, scope, jsonObject.toString(), (context, scriptable, scriptable1, objects) -> objects[1]); - return param; - } - - private Object makeParamArray(JSONArray jsonArray, Context rhino, Scriptable scope) { - //Object param = NativeJSON.parse(rhino, scope, "{myarray: " + jsonArray.toString() + " }", new Callable() { - Object param = NativeJSON.parse(rhino, scope, jsonArray.toString(), (context, scriptable, scriptable1, objects) -> objects[1]); - return param; - } - - private String readFile(String filename) throws IOException { - byte[] bytes = mScriptReader.readFile(filename); - String string = new String(bytes, StandardCharsets.UTF_8); - if (string.startsWith("#!/usr/bin/env node")) { - string = string.substring(20); - } - return string; - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.kt new file mode 100644 index 0000000000..ee0be787e4 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.kt @@ -0,0 +1,238 @@ +package info.nightscout.androidaps.plugins.aps.openAPSAMA + +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.Constants +import info.nightscout.androidaps.R +import info.nightscout.androidaps.data.IobTotal +import info.nightscout.androidaps.data.MealData +import info.nightscout.androidaps.data.Profile +import info.nightscout.androidaps.interfaces.ProfileFunction +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.aps.logger.LoggerCallback +import info.nightscout.androidaps.plugins.aps.loop.ScriptReader +import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults +import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker +import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansUploader +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.utils.sharedPreferences.SP +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import org.mozilla.javascript.* +import org.mozilla.javascript.Function +import java.io.IOException +import java.lang.reflect.InvocationTargetException +import java.nio.charset.StandardCharsets +import javax.inject.Inject +import kotlin.math.min + +class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader, injector: HasAndroidInjector) { + + private val injector: HasAndroidInjector + + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var constraintChecker: ConstraintChecker + @Inject lateinit var sp: SP + @Inject lateinit var profileFunction: ProfileFunction + @Inject lateinit var treatmentsPlugin: TreatmentsPlugin + @Inject lateinit var openHumansUploader: OpenHumansUploader + + private val mScriptReader: ScriptReader + private var profile = JSONObject() + private var glucoseStatus = JSONObject() + private var iobData: JSONArray? = null + private var mealData = JSONObject() + private var currentTemp = JSONObject() + private var autosensData = JSONObject() + + var currentTempParam: String? = null + private set + var iobDataParam: String? = null + private set + var glucoseStatusParam: String? = null + private set + var profileParam: String? = null + private set + var mealDataParam: String? = null + private set + var scriptDebug = "" + private set + + operator fun invoke(): DetermineBasalResultAMA? { + aapsLogger.debug(LTag.APS, ">>> Invoking determine_basal <<<") + aapsLogger.debug(LTag.APS, "Glucose status: " + glucoseStatus.toString().also { glucoseStatusParam = it }) + aapsLogger.debug(LTag.APS, "IOB data: " + iobData.toString().also { iobDataParam = it }) + aapsLogger.debug(LTag.APS, "Current temp: " + currentTemp.toString().also { currentTempParam = it }) + aapsLogger.debug(LTag.APS, "Profile: " + profile.toString().also { profileParam = it }) + aapsLogger.debug(LTag.APS, "Meal data: " + mealData.toString().also { mealDataParam = it }) + aapsLogger.debug(LTag.APS, "Autosens data: $autosensData") + var determineBasalResultAMA: DetermineBasalResultAMA? = null + val rhino = Context.enter() + val scope: Scriptable = rhino.initStandardObjects() + // Turn off optimization to make Rhino Android compatible + rhino.optimizationLevel = -1 + try { + + //register logger callback for console.log and console.error + ScriptableObject.defineClass(scope, LoggerCallback::class.java) + val myLogger = rhino.newObject(scope, "LoggerCallback", null) + scope.put("console2", scope, myLogger) + rhino.evaluateString(scope, readFile("OpenAPSAMA/loggerhelper.js"), "JavaScript", 0, null) + + //set module parent + rhino.evaluateString(scope, "var module = {\"parent\":Boolean(1)};", "JavaScript", 0, null) + rhino.evaluateString(scope, "var round_basal = function round_basal(basal, profile) { return basal; };", "JavaScript", 0, null) + rhino.evaluateString(scope, "require = function() {return round_basal;};", "JavaScript", 0, null) + + //generate functions "determine_basal" and "setTempBasal" + rhino.evaluateString(scope, readFile("OpenAPSAMA/determine-basal.js"), "JavaScript", 0, null) + rhino.evaluateString(scope, readFile("OpenAPSAMA/basal-set-temp.js"), "setTempBasal.js", 0, null) + val determineBasalObj = scope["determine_basal", scope] + val setTempBasalFunctionsObj = scope["tempBasalFunctions", scope] + + //call determine-basal + if (determineBasalObj is Function && setTempBasalFunctionsObj is NativeObject) { + + //prepare parameters + val params = arrayOf( + makeParam(glucoseStatus, rhino, scope), + makeParam(currentTemp, rhino, scope), + makeParamArray(iobData, rhino, scope), + makeParam(profile, rhino, scope), + makeParam(autosensData, rhino, scope), + makeParam(mealData, rhino, scope), + setTempBasalFunctionsObj) + val jsResult = determineBasalObj.call(rhino, scope, scope, params) as NativeObject + scriptDebug = LoggerCallback.scriptDebug + + // Parse the jsResult object to a JSON-String + val result = NativeJSON.stringify(rhino, scope, jsResult, null, null).toString() + aapsLogger.debug(LTag.APS, "Result: $result") + try { + val resultJson = JSONObject(result) + openHumansUploader.enqueueAMAData(profile, glucoseStatus, iobData, mealData, currentTemp, autosensData, resultJson) + determineBasalResultAMA = DetermineBasalResultAMA(injector, jsResult, resultJson) + } catch (e: JSONException) { + aapsLogger.error(LTag.APS, "Unhandled exception", e) + } + } else { + aapsLogger.error(LTag.APS, "Problem loading JS Functions") + } + } catch (e: IOException) { + aapsLogger.error(LTag.APS, "IOException") + } catch (e: RhinoException) { + aapsLogger.error(LTag.APS, "RhinoException: (" + e.lineNumber() + "," + e.columnNumber() + ") " + e.toString()) + } catch (e: IllegalAccessException) { + aapsLogger.error(LTag.APS, e.toString()) + } catch (e: InstantiationException) { + aapsLogger.error(LTag.APS, e.toString()) + } catch (e: InvocationTargetException) { + aapsLogger.error(LTag.APS, e.toString()) + } finally { + Context.exit() + } + glucoseStatusParam = glucoseStatus.toString() + iobDataParam = iobData.toString() + currentTempParam = currentTemp.toString() + profileParam = profile.toString() + mealDataParam = mealData.toString() + return determineBasalResultAMA + } + + @Throws(JSONException::class) fun setData(profile: Profile, + maxIob: Double, + maxBasal: Double, + minBg: Double, + maxBg: Double, + targetBg: Double, + basalRate: Double, + iobArray: Array?, + glucoseStatus: GlucoseStatus, + mealData: MealData, + autosensDataRatio: Double, + tempTargetSet: Boolean) { + this.profile = JSONObject() + this.profile.put("max_iob", maxIob) + this.profile.put("dia", min(profile.dia, 3.0)) + this.profile.put("type", "current") + this.profile.put("max_daily_basal", profile.maxDailyBasal) + this.profile.put("max_basal", maxBasal) + this.profile.put("min_bg", minBg) + this.profile.put("max_bg", maxBg) + this.profile.put("target_bg", targetBg) + this.profile.put("carb_ratio", profile.ic) + this.profile.put("sens", profile.isfMgdl) + this.profile.put("max_daily_safety_multiplier", sp.getInt(R.string.key_openapsama_max_daily_safety_multiplier, 3)) + this.profile.put("current_basal_safety_multiplier", sp.getDouble(R.string.key_openapsama_current_basal_safety_multiplier, 4.0)) + this.profile.put("skip_neutral_temps", true) + this.profile.put("current_basal", basalRate) + this.profile.put("temptargetSet", tempTargetSet) + this.profile.put("autosens_adjust_targets", sp.getBoolean(R.string.key_openapsama_autosens_adjusttargets, true)) + //align with max-absorption model in AMA sensitivity + if (mealData.usedMinCarbsImpact > 0) { + this.profile.put("min_5m_carbimpact", mealData.usedMinCarbsImpact) + } else { + this.profile.put("min_5m_carbimpact", sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact)) + } + if (profileFunction.getUnits() == Constants.MMOL) { + this.profile.put("out_units", "mmol/L") + } + val now = System.currentTimeMillis() + val tb = treatmentsPlugin.getTempBasalFromHistory(now) + currentTemp = JSONObject() + currentTemp.put("temp", "absolute") + currentTemp.put("duration", tb?.plannedRemainingMinutes ?: 0) + currentTemp.put("rate", tb?.tempBasalConvertedToAbsolute(now, profile) ?: 0.0) + + // as we have non default temps longer than 30 minutes + val tempBasal = treatmentsPlugin.getTempBasalFromHistory(System.currentTimeMillis()) + if (tempBasal != null) { + currentTemp.put("minutesrunning", tempBasal.realDuration) + } + iobData = IobCobCalculatorPlugin.convertToJSONArray(iobArray) + this.glucoseStatus = JSONObject() + this.glucoseStatus.put("glucose", glucoseStatus.glucose) + if (sp.getBoolean(R.string.key_always_use_shortavg, false)) { + this.glucoseStatus.put("delta", glucoseStatus.short_avgdelta) + } else { + this.glucoseStatus.put("delta", glucoseStatus.delta) + } + this.glucoseStatus.put("short_avgdelta", glucoseStatus.short_avgdelta) + this.glucoseStatus.put("long_avgdelta", glucoseStatus.long_avgdelta) + this.mealData = JSONObject() + this.mealData.put("carbs", mealData.carbs) + this.mealData.put("boluses", mealData.boluses) + this.mealData.put("mealCOB", mealData.mealCOB) + if (constraintChecker.isAutosensModeEnabled().value()) { + autosensData.put("ratio", autosensDataRatio) + } else { + autosensData.put("ratio", 1.0) + } + } + + private fun makeParam(jsonObject: JSONObject?, rhino: Context, scope: Scriptable): Any { + return if (jsonObject == null) Undefined.instance else NativeJSON.parse(rhino, scope, jsonObject.toString()) { _: Context?, _: Scriptable?, _: Scriptable?, objects: Array -> objects[1] } + } + + private fun makeParamArray(jsonArray: JSONArray?, rhino: Context, scope: Scriptable): Any { + return NativeJSON.parse(rhino, scope, jsonArray.toString()) { _: Context?, _: Scriptable?, _: Scriptable?, objects: Array -> objects[1] } + } + + @Throws(IOException::class) private fun readFile(filename: String): String { + val bytes = mScriptReader.readFile(filename) + var string = String(bytes, StandardCharsets.UTF_8) + if (string.startsWith("#!/usr/bin/env node")) { + string = string.substring(20) + } + return string + } + + init { + injector.androidInjector().inject(this) + mScriptReader = scriptReader + this.injector = injector + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAFragment.kt index abe789c79c..84bc71daf7 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAFragment.kt @@ -114,7 +114,7 @@ class OpenAPSAMAFragment : DaggerFragment() { if (openAPSAMAPlugin.lastAPSRun != 0L) { binding.lastrun.text = dateUtil.dateAndTimeString(openAPSAMAPlugin.lastAPSRun) } - openAPSAMAPlugin.lastAutosensResult?.let { + openAPSAMAPlugin.lastAutosensResult.let { binding.autosensdata.text = JSONFormatter.format(it.json()) } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.java deleted file mode 100644 index 76e546b88d..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.java +++ /dev/null @@ -1,263 +0,0 @@ -package info.nightscout.androidaps.plugins.aps.openAPSAMA; - -import android.content.Context; - -import org.json.JSONException; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import dagger.android.HasAndroidInjector; -import info.nightscout.androidaps.R; -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.ActivePluginProvider; -import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.interfaces.PluginDescription; -import info.nightscout.androidaps.interfaces.PluginType; -import info.nightscout.androidaps.interfaces.ProfileFunction; -import info.nightscout.androidaps.interfaces.PumpInterface; -import info.nightscout.androidaps.logging.AAPSLogger; -import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateGui; -import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateResultGui; -import info.nightscout.androidaps.plugins.aps.loop.APSResult; -import info.nightscout.androidaps.plugins.aps.loop.ScriptReader; -import info.nightscout.androidaps.plugins.bus.RxBusWrapper; -import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData; -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.FabricPrivacy; -import info.nightscout.androidaps.utils.HardLimits; -import info.nightscout.androidaps.utils.Profiler; -import info.nightscout.androidaps.utils.Round; -import info.nightscout.androidaps.utils.resources.ResourceHelper; - -@Singleton -public class OpenAPSAMAPlugin extends PluginBase implements APSInterface { - private final AAPSLogger aapsLogger; - private final RxBusWrapper rxBus; - private final ConstraintChecker constraintChecker; - private final ResourceHelper resourceHelper; - private final ProfileFunction profileFunction; - private final Context context; - private final ActivePluginProvider activePlugin; - private final TreatmentsPlugin treatmentsPlugin; - private final IobCobCalculatorPlugin iobCobCalculatorPlugin; - private final HardLimits hardLimits; - private final Profiler profiler; - private final FabricPrivacy fabricPrivacy; - - // last values - DetermineBasalAdapterAMAJS lastDetermineBasalAdapterAMAJS = null; - long lastAPSRun = 0; - DetermineBasalResultAMA lastAPSResult = null; - AutosensResult lastAutosensResult = null; - - @Inject - public OpenAPSAMAPlugin( - HasAndroidInjector injector, - AAPSLogger aapsLogger, - RxBusWrapper rxBus, - ConstraintChecker constraintChecker, - ResourceHelper resourceHelper, - ProfileFunction profileFunction, - Context context, - ActivePluginProvider activePlugin, - TreatmentsPlugin treatmentsPlugin, - IobCobCalculatorPlugin iobCobCalculatorPlugin, - HardLimits hardLimits, - Profiler profiler, - FabricPrivacy fabricPrivacy - ) { - super(new PluginDescription() - .mainType(PluginType.APS) - .fragmentClass(OpenAPSAMAFragment.class.getName()) - .pluginIcon(R.drawable.ic_generic_icon) - .pluginName(R.string.openapsama) - .shortName(R.string.oaps_shortname) - .preferencesId(R.xml.pref_openapsama) - .description(R.string.description_ama), - aapsLogger, resourceHelper, injector - ); - this.aapsLogger = aapsLogger; - this.rxBus = rxBus; - this.constraintChecker = constraintChecker; - this.resourceHelper = resourceHelper; - this.profileFunction = profileFunction; - this.context = context; - this.activePlugin = activePlugin; - this.treatmentsPlugin = treatmentsPlugin; - this.iobCobCalculatorPlugin = iobCobCalculatorPlugin; - this.hardLimits = hardLimits; - this.profiler = profiler; - this.fabricPrivacy = fabricPrivacy; - } - - @Override - public boolean specialEnableCondition() { - try { - PumpInterface pump = activePlugin.getActivePump(); - return pump.getPumpDescription().isTempBasalCapable; - } catch (Exception ignored) { - // may fail during initialization - return true; - } - } - - @Override - public boolean specialShowInListCondition() { - PumpInterface pump = activePlugin.getActivePump(); - return pump.getPumpDescription().isTempBasalCapable; - } - - @Override - public APSResult getLastAPSResult() { - return lastAPSResult; - } - - @Override - public long getLastAPSRun() { - return lastAPSRun; - } - - @Override - public void invoke(String initiator, boolean tempBasalFallback) { - aapsLogger.debug(LTag.APS, "invoke from " + initiator + " tempBasalFallback: " + tempBasalFallback); - lastAPSResult = null; - DetermineBasalAdapterAMAJS determineBasalAdapterAMAJS; - determineBasalAdapterAMAJS = new DetermineBasalAdapterAMAJS(new ScriptReader(context), getInjector()); - - GlucoseStatus glucoseStatus = new GlucoseStatus(getInjector()).getGlucoseStatusData(); - Profile profile = profileFunction.getProfile(); - PumpInterface pump = activePlugin.getActivePump(); - - if (profile == null) { - rxBus.send(new EventOpenAPSUpdateResultGui(resourceHelper.gs(R.string.noprofileselected))); - aapsLogger.debug(LTag.APS, resourceHelper.gs(R.string.noprofileselected)); - return; - } - - if (!isEnabled(PluginType.APS)) { - rxBus.send(new EventOpenAPSUpdateResultGui(resourceHelper.gs(R.string.openapsma_disabled))); - aapsLogger.debug(LTag.APS, resourceHelper.gs(R.string.openapsma_disabled)); - return; - } - - if (glucoseStatus == null) { - rxBus.send(new EventOpenAPSUpdateResultGui(resourceHelper.gs(R.string.openapsma_noglucosedata))); - aapsLogger.debug(LTag.APS, resourceHelper.gs(R.string.openapsma_noglucosedata)); - return; - } - - double maxBasal = constraintChecker.getMaxBasalAllowed(profile).value(); - double minBg = profile.getTargetLowMgdl(); - double maxBg = profile.getTargetHighMgdl(); - double targetBg = profile.getTargetMgdl(); - - minBg = Round.roundTo(minBg, 0.1d); - maxBg = Round.roundTo(maxBg, 0.1d); - - long start = System.currentTimeMillis(); - long startPart = System.currentTimeMillis(); - IobTotal[] iobArray = iobCobCalculatorPlugin.calculateIobArrayInDia(profile); - profiler.log(LTag.APS, "calculateIobArrayInDia()", startPart); - - startPart = System.currentTimeMillis(); - MealData mealData = iobCobCalculatorPlugin.getMealData(); - profiler.log(LTag.APS, "getMealData()", startPart); - - double maxIob = constraintChecker.getMaxIOBAllowed().value(); - - minBg = hardLimits.verifyHardLimits(minBg, "minBg", hardLimits.getVERY_HARD_LIMIT_MIN_BG()[0], hardLimits.getVERY_HARD_LIMIT_MIN_BG()[1]); - maxBg = hardLimits.verifyHardLimits(maxBg, "maxBg", hardLimits.getVERY_HARD_LIMIT_MAX_BG()[0], hardLimits.getVERY_HARD_LIMIT_MAX_BG()[1]); - targetBg = hardLimits.verifyHardLimits(targetBg, "targetBg", hardLimits.getVERY_HARD_LIMIT_TARGET_BG()[0], hardLimits.getVERY_HARD_LIMIT_TARGET_BG()[1]); - - boolean isTempTarget = false; - TempTarget tempTarget = treatmentsPlugin.getTempTargetFromHistory(System.currentTimeMillis()); - if (tempTarget != null) { - isTempTarget = true; - minBg = hardLimits.verifyHardLimits(tempTarget.low, "minBg", hardLimits.getVERY_HARD_LIMIT_TEMP_MIN_BG()[0], hardLimits.getVERY_HARD_LIMIT_TEMP_MIN_BG()[1]); - maxBg = hardLimits.verifyHardLimits(tempTarget.high, "maxBg", hardLimits.getVERY_HARD_LIMIT_TEMP_MAX_BG()[0], hardLimits.getVERY_HARD_LIMIT_TEMP_MAX_BG()[1]); - targetBg = hardLimits.verifyHardLimits(tempTarget.target(), "targetBg", hardLimits.getVERY_HARD_LIMIT_TEMP_TARGET_BG()[0], hardLimits.getVERY_HARD_LIMIT_TEMP_TARGET_BG()[1]); - } - - - if (!hardLimits.checkOnlyHardLimits(profile.getDia(), "dia", hardLimits.minDia(), hardLimits.maxDia())) - return; - if (!hardLimits.checkOnlyHardLimits(profile.getIcTimeFromMidnight(Profile.secondsFromMidnight()), "carbratio", hardLimits.minIC(), hardLimits.maxIC())) - return; - if (!hardLimits.checkOnlyHardLimits(profile.getIsfMgdl(), "sens", hardLimits.getMINISF(), hardLimits.getMAXISF())) - return; - if (!hardLimits.checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.02, hardLimits.maxBasal())) - return; - if (!hardLimits.checkOnlyHardLimits(pump.getBaseBasalRate(), "current_basal", 0.01, hardLimits.maxBasal())) - return; - - startPart = System.currentTimeMillis(); - if (constraintChecker.isAutosensModeEnabled().value()) { - AutosensData autosensData = iobCobCalculatorPlugin.getLastAutosensDataSynchronized("OpenAPSPlugin"); - if (autosensData == null) { - rxBus.send(new EventOpenAPSUpdateResultGui(resourceHelper.gs(R.string.openaps_noasdata))); - return; - } - lastAutosensResult = autosensData.autosensResult; - } else { - lastAutosensResult = new AutosensResult(); - lastAutosensResult.sensResult = "autosens disabled"; - } - profiler.log(LTag.APS, "detectSensitivityandCarbAbsorption()", startPart); - profiler.log(LTag.APS, "AMA data gathering", start); - - start = System.currentTimeMillis(); - - try { - determineBasalAdapterAMAJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg, activePlugin.getActivePump().getBaseBasalRate(), iobArray, glucoseStatus, mealData, - lastAutosensResult.ratio, //autosensDataRatio - isTempTarget - ); - } catch (JSONException e) { - fabricPrivacy.logException(e); - return; - } - - - DetermineBasalResultAMA determineBasalResultAMA = determineBasalAdapterAMAJS.invoke(); - profiler.log(LTag.APS, "AMA calculation", start); - // Fix bug determine basal - if (determineBasalResultAMA == null) { - aapsLogger.error(LTag.APS, "SMB calculation returned null"); - lastDetermineBasalAdapterAMAJS = null; - lastAPSResult = null; - lastAPSRun = 0; - } else { - if (determineBasalResultAMA.getRate() == 0d && determineBasalResultAMA.getDuration() == 0 && !treatmentsPlugin.isTempBasalInProgress()) - determineBasalResultAMA.setTempBasalRequested(false); - - determineBasalResultAMA.setIob(iobArray[0]); - - long now = System.currentTimeMillis(); - - try { - determineBasalResultAMA.getJson().put("timestamp", DateUtil.toISOString(now)); - } catch (JSONException e) { - aapsLogger.error(LTag.APS, "Unhandled exception", e); - } - - lastDetermineBasalAdapterAMAJS = determineBasalAdapterAMAJS; - lastAPSResult = determineBasalResultAMA; - lastAPSRun = now; - } - rxBus.send(new EventOpenAPSUpdateGui()); - - //deviceStatus.suggested = determineBasalResultAMA.json; - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.kt new file mode 100644 index 0000000000..97a6079e4a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.kt @@ -0,0 +1,172 @@ +package info.nightscout.androidaps.plugins.aps.openAPSAMA + +import android.content.Context +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.R +import info.nightscout.androidaps.data.Profile +import info.nightscout.androidaps.interfaces.* +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateGui +import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateResultGui +import info.nightscout.androidaps.plugins.aps.loop.ScriptReader +import info.nightscout.androidaps.plugins.bus.RxBusWrapper +import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.HardLimits +import info.nightscout.androidaps.utils.Profiler +import info.nightscout.androidaps.utils.Round +import info.nightscout.androidaps.utils.resources.ResourceHelper +import org.json.JSONException +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +open class OpenAPSAMAPlugin @Inject constructor( + injector: HasAndroidInjector, + aapsLogger: AAPSLogger, + private val rxBus: RxBusWrapper, + private val constraintChecker: ConstraintChecker, + resourceHelper: ResourceHelper, + private val profileFunction: ProfileFunction, + private val context: Context, + private val activePlugin: ActivePluginProvider, + private val treatmentsPlugin: TreatmentsPlugin, + private val iobCobCalculatorPlugin: IobCobCalculatorPlugin, + private val hardLimits: HardLimits, + private val profiler: Profiler, + private val fabricPrivacy: FabricPrivacy +) : PluginBase(PluginDescription() + .mainType(PluginType.APS) + .fragmentClass(OpenAPSAMAFragment::class.java.name) + .pluginIcon(R.drawable.ic_generic_icon) + .pluginName(R.string.openapsama) + .shortName(R.string.oaps_shortname) + .preferencesId(R.xml.pref_openapsama) + .description(R.string.description_ama), + aapsLogger, resourceHelper, injector +), APSInterface { + + // last values + override var lastAPSRun: Long = 0 + override var lastAPSResult: DetermineBasalResultAMA? = null + var lastDetermineBasalAdapterAMAJS: DetermineBasalAdapterAMAJS? = null + var lastAutosensResult: AutosensResult = AutosensResult() + + override fun specialEnableCondition(): Boolean { + return try { + val pump = activePlugin.activePump + pump.pumpDescription.isTempBasalCapable + } catch (ignored: Exception) { + // may fail during initialization + true + } + } + + override fun specialShowInListCondition(): Boolean { + val pump = activePlugin.activePump + return pump.pumpDescription.isTempBasalCapable + } + + override fun invoke(initiator: String, tempBasalFallback: Boolean) { + aapsLogger.debug(LTag.APS, "invoke from $initiator tempBasalFallback: $tempBasalFallback") + lastAPSResult = null + val determineBasalAdapterAMAJS = DetermineBasalAdapterAMAJS(ScriptReader(context), injector) + val glucoseStatus = GlucoseStatus(injector).glucoseStatusData + val profile = profileFunction.getProfile() + val pump = activePlugin.activePump + if (profile == null) { + rxBus.send(EventOpenAPSUpdateResultGui(resourceHelper.gs(R.string.noprofileselected))) + aapsLogger.debug(LTag.APS, resourceHelper.gs(R.string.noprofileselected)) + return + } + if (!isEnabled(PluginType.APS)) { + rxBus.send(EventOpenAPSUpdateResultGui(resourceHelper.gs(R.string.openapsma_disabled))) + aapsLogger.debug(LTag.APS, resourceHelper.gs(R.string.openapsma_disabled)) + return + } + if (glucoseStatus == null) { + rxBus.send(EventOpenAPSUpdateResultGui(resourceHelper.gs(R.string.openapsma_noglucosedata))) + aapsLogger.debug(LTag.APS, resourceHelper.gs(R.string.openapsma_noglucosedata)) + return + } + val inputConstraints = Constraint(0.0) // fake. only for collecting all results + val maxBasal = constraintChecker.getMaxBasalAllowed(profile).also { + inputConstraints.copyReasons(it) + }.value() + var start = System.currentTimeMillis() + var startPart = System.currentTimeMillis() + val iobArray = iobCobCalculatorPlugin.calculateIobArrayInDia(profile) + profiler.log(LTag.APS, "calculateIobArrayInDia()", startPart) + startPart = System.currentTimeMillis() + val mealData = iobCobCalculatorPlugin.mealData + profiler.log(LTag.APS, "getMealData()", startPart) + val maxIob = constraintChecker.getMaxIOBAllowed().also { maxIOBAllowedConstraint -> + inputConstraints.copyReasons(maxIOBAllowedConstraint) + }.value() + var minBg = hardLimits.verifyHardLimits(Round.roundTo(profile.targetLowMgdl, 0.1), "minBg", hardLimits.VERY_HARD_LIMIT_MIN_BG[0].toDouble(), hardLimits.VERY_HARD_LIMIT_MIN_BG[1].toDouble()) + var maxBg = hardLimits.verifyHardLimits(Round.roundTo(profile.targetHighMgdl, 0.1), "maxBg", hardLimits.VERY_HARD_LIMIT_MAX_BG[0].toDouble(), hardLimits.VERY_HARD_LIMIT_MAX_BG[1].toDouble()) + var targetBg = hardLimits.verifyHardLimits(profile.targetMgdl, "targetBg", hardLimits.VERY_HARD_LIMIT_TARGET_BG[0].toDouble(), hardLimits.VERY_HARD_LIMIT_TARGET_BG[1].toDouble()) + var isTempTarget = false + treatmentsPlugin.getTempTargetFromHistory(System.currentTimeMillis())?.let { tempTarget -> + isTempTarget = true + minBg = hardLimits.verifyHardLimits(tempTarget.low, "minBg", hardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[0].toDouble(), hardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[1].toDouble()) + maxBg = hardLimits.verifyHardLimits(tempTarget.high, "maxBg", hardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0].toDouble(), hardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1].toDouble()) + targetBg = hardLimits.verifyHardLimits(tempTarget.target(), "targetBg", hardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[0].toDouble(), hardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[1].toDouble()) + } + if (!hardLimits.checkOnlyHardLimits(profile.dia, "dia", hardLimits.minDia(), hardLimits.maxDia())) return + if (!hardLimits.checkOnlyHardLimits(profile.getIcTimeFromMidnight(Profile.secondsFromMidnight()), "carbratio", hardLimits.minIC(), hardLimits.maxIC())) return + if (!hardLimits.checkOnlyHardLimits(profile.isfMgdl, "sens", hardLimits.MINISF, hardLimits.MAXISF)) return + if (!hardLimits.checkOnlyHardLimits(profile.maxDailyBasal, "max_daily_basal", 0.02, hardLimits.maxBasal())) return + if (!hardLimits.checkOnlyHardLimits(pump.baseBasalRate, "current_basal", 0.01, hardLimits.maxBasal())) return + startPart = System.currentTimeMillis() + if (constraintChecker.isAutosensModeEnabled().value()) { + val autosensData = iobCobCalculatorPlugin.getLastAutosensDataSynchronized("OpenAPSPlugin") + if (autosensData == null) { + rxBus.send(EventOpenAPSUpdateResultGui(resourceHelper.gs(R.string.openaps_noasdata))) + return + } + lastAutosensResult = autosensData.autosensResult + } else { + lastAutosensResult.sensResult = "autosens disabled" + } + profiler.log(LTag.APS, "detectSensitivityAndCarbAbsorption()", startPart) + profiler.log(LTag.APS, "AMA data gathering", start) + start = System.currentTimeMillis() + try { + determineBasalAdapterAMAJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg, activePlugin.activePump.baseBasalRate, iobArray, glucoseStatus, mealData, + lastAutosensResult.ratio, + isTempTarget + ) + } catch (e: JSONException) { + fabricPrivacy.logException(e) + return + } + val determineBasalResultAMA = determineBasalAdapterAMAJS.invoke() + profiler.log(LTag.APS, "AMA calculation", start) + // Fix bug determine basal + if (determineBasalResultAMA == null) { + aapsLogger.error(LTag.APS, "SMB calculation returned null") + lastDetermineBasalAdapterAMAJS = null + lastAPSResult = null + lastAPSRun = 0 + } else { + if (determineBasalResultAMA.rate == 0.0 && determineBasalResultAMA.duration == 0 && !treatmentsPlugin.isTempBasalInProgress) determineBasalResultAMA.tempBasalRequested = false + determineBasalResultAMA.iob = iobArray[0] + val now = System.currentTimeMillis() + determineBasalResultAMA.json?.put("timestamp", DateUtil.toISOString(now)) + determineBasalResultAMA.inputConstraints = inputConstraints + lastDetermineBasalAdapterAMAJS = determineBasalAdapterAMAJS + lastAPSResult = determineBasalResultAMA + lastAPSRun = now + } + rxBus.send(EventOpenAPSUpdateGui()) + + //deviceStatus.suggested = determineBasalResultAMA.json; + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.java deleted file mode 100644 index 5793c7125f..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.java +++ /dev/null @@ -1,373 +0,0 @@ -package info.nightscout.androidaps.plugins.aps.openAPSSMB; - -import androidx.annotation.Nullable; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.mozilla.javascript.Context; -import org.mozilla.javascript.Function; -import org.mozilla.javascript.NativeJSON; -import org.mozilla.javascript.NativeObject; -import org.mozilla.javascript.RhinoException; -import org.mozilla.javascript.Scriptable; -import org.mozilla.javascript.ScriptableObject; -import org.mozilla.javascript.Undefined; - -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.nio.charset.StandardCharsets; - -import javax.inject.Inject; - -import dagger.android.HasAndroidInjector; -import info.nightscout.androidaps.Constants; -import info.nightscout.androidaps.R; -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.ActivePluginProvider; -import info.nightscout.androidaps.interfaces.ProfileFunction; -import info.nightscout.androidaps.interfaces.PumpInterface; -import info.nightscout.androidaps.logging.AAPSLogger; -import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.plugins.aps.logger.LoggerCallback; -import info.nightscout.androidaps.plugins.aps.loop.ScriptReader; -import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker; -import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansUploader; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; -import info.nightscout.androidaps.utils.SafeParse; -import info.nightscout.androidaps.utils.resources.ResourceHelper; -import info.nightscout.androidaps.utils.sharedPreferences.SP; - - -public class DetermineBasalAdapterSMBJS { - private final HasAndroidInjector injector; - @Inject AAPSLogger aapsLogger; - @Inject ConstraintChecker constraintChecker; - @Inject SP sp; - @Inject ResourceHelper resourceHelper; - @Inject ProfileFunction profileFunction; - @Inject TreatmentsPlugin treatmentsPlugin; - @Inject ActivePluginProvider activePluginProvider; - @Inject OpenHumansUploader openHumansUploader; - - - private final ScriptReader mScriptReader; - private JSONObject mProfile; - private JSONObject mGlucoseStatus; - private JSONArray mIobData; - private JSONObject mMealData; - private JSONObject mCurrentTemp; - private JSONObject mAutosensData = null; - private boolean mMicrobolusAllowed; - private boolean mSMBAlwaysAllowed; - private long mCurrentTime; - private boolean mIsSaveCgmSource; - - private String storedCurrentTemp = null; - private String storedIobData = null; - - private String storedGlucoseStatus = null; - private String storedProfile = null; - private String storedMeal_data = null; - - private String scriptDebug = ""; - - /** - * Main code - */ - - DetermineBasalAdapterSMBJS(ScriptReader scriptReader, HasAndroidInjector injector) { - mScriptReader = scriptReader; - this.injector = injector; - injector.androidInjector().inject(this); - } - - - @Nullable - public DetermineBasalResultSMB invoke() { - - - aapsLogger.debug(LTag.APS, ">>> Invoking detemine_basal <<<"); - aapsLogger.debug(LTag.APS, "Glucose status: " + (storedGlucoseStatus = mGlucoseStatus.toString())); - aapsLogger.debug(LTag.APS, "IOB data: " + (storedIobData = mIobData.toString())); - aapsLogger.debug(LTag.APS, "Current temp: " + (storedCurrentTemp = mCurrentTemp.toString())); - aapsLogger.debug(LTag.APS, "Profile: " + (storedProfile = mProfile.toString())); - aapsLogger.debug(LTag.APS, "Meal data: " + (storedMeal_data = mMealData.toString())); - if (mAutosensData != null) - aapsLogger.debug(LTag.APS, "Autosens data: " + mAutosensData.toString()); - else - aapsLogger.debug(LTag.APS, "Autosens data: " + "undefined"); - aapsLogger.debug(LTag.APS, "Reservoir data: " + "undefined"); - aapsLogger.debug(LTag.APS, "MicroBolusAllowed: " + mMicrobolusAllowed); - aapsLogger.debug(LTag.APS, "SMBAlwaysAllowed: " + mSMBAlwaysAllowed); - aapsLogger.debug(LTag.APS, "CurrentTime: " + mCurrentTime); - aapsLogger.debug(LTag.APS, "isSaveCgmSource: " + mIsSaveCgmSource); - - - DetermineBasalResultSMB determineBasalResultSMB = null; - - Context rhino = Context.enter(); - Scriptable scope = rhino.initStandardObjects(); - // Turn off optimization to make Rhino Android compatible - rhino.setOptimizationLevel(-1); - - try { - - //register logger callback for console.log and console.error - ScriptableObject.defineClass(scope, LoggerCallback.class); - Scriptable myLogger = rhino.newObject(scope, "LoggerCallback", null); - scope.put("console2", scope, myLogger); - rhino.evaluateString(scope, readFile("OpenAPSAMA/loggerhelper.js"), "JavaScript", 0, null); - - //set module parent - rhino.evaluateString(scope, "var module = {\"parent\":Boolean(1)};", "JavaScript", 0, null); - rhino.evaluateString(scope, "var round_basal = function round_basal(basal, profile) { return basal; };", "JavaScript", 0, null); - rhino.evaluateString(scope, "require = function() {return round_basal;};", "JavaScript", 0, null); - - //generate functions "determine_basal" and "setTempBasal" - rhino.evaluateString(scope, readFile("OpenAPSSMB/determine-basal.js"), "JavaScript", 0, null); - rhino.evaluateString(scope, readFile("OpenAPSSMB/basal-set-temp.js"), "setTempBasal.js", 0, null); - Object determineBasalObj = scope.get("determine_basal", scope); - Object setTempBasalFunctionsObj = scope.get("tempBasalFunctions", scope); - - //call determine-basal - if (determineBasalObj instanceof Function && setTempBasalFunctionsObj instanceof NativeObject) { - Function determineBasalJS = (Function) determineBasalObj; - - //prepare parameters - Object[] params = new Object[]{ - makeParam(mGlucoseStatus, rhino, scope), - makeParam(mCurrentTemp, rhino, scope), - makeParamArray(mIobData, rhino, scope), - makeParam(mProfile, rhino, scope), - makeParam(mAutosensData, rhino, scope), - makeParam(mMealData, rhino, scope), - setTempBasalFunctionsObj, - Boolean.valueOf(mMicrobolusAllowed), - makeParam(null, rhino, scope), // reservoir data as undefined - Long.valueOf(mCurrentTime), - Boolean.valueOf(mIsSaveCgmSource) - }; - - - NativeObject jsResult = (NativeObject) determineBasalJS.call(rhino, scope, scope, params); - scriptDebug = LoggerCallback.getScriptDebug(); - - // Parse the jsResult object to a JSON-String - String result = NativeJSON.stringify(rhino, scope, jsResult, null, null).toString(); - aapsLogger.debug(LTag.APS, "Result: " + result); - try { - JSONObject resultJson = new JSONObject(result); - openHumansUploader.enqueueSMBData(mProfile, mGlucoseStatus, mIobData, mMealData, mCurrentTemp, mAutosensData, mMicrobolusAllowed, mSMBAlwaysAllowed, resultJson); - determineBasalResultSMB = new DetermineBasalResultSMB(injector, resultJson); - } catch (JSONException e) { - aapsLogger.error(LTag.APS, "Unhandled exception", e); - } - } else { - aapsLogger.error(LTag.APS, "Problem loading JS Functions"); - } - } catch (IOException e) { - aapsLogger.error(LTag.APS, "IOException"); - } catch (RhinoException e) { - aapsLogger.error(LTag.APS, "RhinoException: (" + e.lineNumber() + "," + e.columnNumber() + ") " + e.toString()); - } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { - aapsLogger.error(LTag.APS, e.toString()); - } finally { - Context.exit(); - } - - storedGlucoseStatus = mGlucoseStatus.toString(); - storedIobData = mIobData.toString(); - storedCurrentTemp = mCurrentTemp.toString(); - storedProfile = mProfile.toString(); - storedMeal_data = mMealData.toString(); - - return determineBasalResultSMB; - - } - - String getGlucoseStatusParam() { - return storedGlucoseStatus; - } - - String getCurrentTempParam() { - return storedCurrentTemp; - } - - String getIobDataParam() { - return storedIobData; - } - - String getProfileParam() { - return storedProfile; - } - - String getMealDataParam() { - return storedMeal_data; - } - - String getScriptDebug() { - return scriptDebug; - } - - public void setData(Profile profile, - double maxIob, - double maxBasal, - double minBg, - double maxBg, - double targetBg, - double basalrate, - IobTotal[] iobArray, - GlucoseStatus glucoseStatus, - MealData mealData, - double autosensDataRatio, - boolean tempTargetSet, - boolean microBolusAllowed, - boolean uamAllowed, - boolean advancedFiltering, - boolean isSaveCgmSource - ) throws JSONException { - - PumpInterface pump = activePluginProvider.getActivePump(); - Double pumpbolusstep = pump.getPumpDescription().bolusStep; - mProfile = new JSONObject(); - - mProfile.put("max_iob", maxIob); - //mProfile.put("dia", profile.getDia()); - mProfile.put("type", "current"); - mProfile.put("max_daily_basal", profile.getMaxDailyBasal()); - mProfile.put("max_basal", maxBasal); - mProfile.put("min_bg", minBg); - mProfile.put("max_bg", maxBg); - mProfile.put("target_bg", targetBg); - mProfile.put("carb_ratio", profile.getIc()); - mProfile.put("sens", profile.getIsfMgdl()); - mProfile.put("max_daily_safety_multiplier", sp.getInt(R.string.key_openapsama_max_daily_safety_multiplier, 3)); - mProfile.put("current_basal_safety_multiplier", sp.getDouble(R.string.key_openapsama_current_basal_safety_multiplier, 4d)); - - //mProfile.put("high_temptarget_raises_sensitivity", SP.getBoolean(R.string.key_high_temptarget_raises_sensitivity, SMBDefaults.high_temptarget_raises_sensitivity)); - mProfile.put("high_temptarget_raises_sensitivity", false); - //mProfile.put("low_temptarget_lowers_sensitivity", SP.getBoolean(R.string.key_low_temptarget_lowers_sensitivity, SMBDefaults.low_temptarget_lowers_sensitivity)); - mProfile.put("low_temptarget_lowers_sensitivity", false); - - - mProfile.put("sensitivity_raises_target", sp.getBoolean(R.string.key_sensitivity_raises_target, SMBDefaults.sensitivity_raises_target)); - mProfile.put("resistance_lowers_target", sp.getBoolean(R.string.key_resistance_lowers_target, SMBDefaults.resistance_lowers_target)); - mProfile.put("adv_target_adjustments", SMBDefaults.adv_target_adjustments); - mProfile.put("exercise_mode", SMBDefaults.exercise_mode); - mProfile.put("half_basal_exercise_target", SMBDefaults.half_basal_exercise_target); - mProfile.put("maxCOB", SMBDefaults.maxCOB); - mProfile.put("skip_neutral_temps", pump.setNeutralTempAtFullHour()); - // min_5m_carbimpact is not used within SMB determinebasal - //if (mealData.usedMinCarbsImpact > 0) { - // mProfile.put("min_5m_carbimpact", mealData.usedMinCarbsImpact); - //} else { - // mProfile.put("min_5m_carbimpact", SP.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact)); - //} - mProfile.put("remainingCarbsCap", SMBDefaults.remainingCarbsCap); - mProfile.put("enableUAM", uamAllowed); - mProfile.put("A52_risk_enable", SMBDefaults.A52_risk_enable); - - boolean smbEnabled = sp.getBoolean(R.string.key_use_smb, false); - mProfile.put("SMBInterval", sp.getInt(R.string.key_smbinterval, SMBDefaults.SMBInterval)); - mProfile.put("enableSMB_with_COB", smbEnabled && sp.getBoolean(R.string.key_enableSMB_with_COB, false)); - mProfile.put("enableSMB_with_temptarget", smbEnabled && sp.getBoolean(R.string.key_enableSMB_with_temptarget, false)); - mProfile.put("allowSMB_with_high_temptarget", smbEnabled && sp.getBoolean(R.string.key_allowSMB_with_high_temptarget, false)); - mProfile.put("enableSMB_always", smbEnabled && sp.getBoolean(R.string.key_enableSMB_always, false) && advancedFiltering); - mProfile.put("enableSMB_after_carbs", smbEnabled && sp.getBoolean(R.string.key_enableSMB_after_carbs, false) && advancedFiltering); - mProfile.put("maxSMBBasalMinutes", sp.getInt(R.string.key_smbmaxminutes, SMBDefaults.maxSMBBasalMinutes)); - mProfile.put("maxUAMSMBBasalMinutes", sp.getInt(R.string.key_uamsmbmaxminutes, SMBDefaults.maxUAMSMBBasalMinutes)); - //set the min SMB amount to be the amount set by the pump. - mProfile.put("bolus_increment", pumpbolusstep); - mProfile.put("carbsReqThreshold", sp.getInt(R.string.key_carbsReqThreshold, SMBDefaults.carbsReqThreshold)); - - mProfile.put("current_basal", basalrate); - mProfile.put("temptargetSet", tempTargetSet); - mProfile.put("autosens_max", SafeParse.stringToDouble(sp.getString(R.string.key_openapsama_autosens_max, "1.2"))); - - if (profileFunction.getUnits().equals(Constants.MMOL)) { - mProfile.put("out_units", "mmol/L"); - } - - - long now = System.currentTimeMillis(); - TemporaryBasal tb = treatmentsPlugin.getTempBasalFromHistory(now); - - mCurrentTemp = new JSONObject(); - mCurrentTemp.put("temp", "absolute"); - mCurrentTemp.put("duration", tb != null ? tb.getPlannedRemainingMinutes() : 0); - mCurrentTemp.put("rate", tb != null ? tb.tempBasalConvertedToAbsolute(now, profile) : 0d); - - // as we have non default temps longer than 30 mintues - TemporaryBasal tempBasal = treatmentsPlugin.getTempBasalFromHistory(System.currentTimeMillis()); - if (tempBasal != null) { - mCurrentTemp.put("minutesrunning", tempBasal.getRealDuration()); - } - - mIobData = IobCobCalculatorPlugin.convertToJSONArray(iobArray); - - mGlucoseStatus = new JSONObject(); - mGlucoseStatus.put("glucose", glucoseStatus.glucose); - mGlucoseStatus.put("noise", glucoseStatus.noise); - - if (sp.getBoolean(R.string.key_always_use_shortavg, false)) { - mGlucoseStatus.put("delta", glucoseStatus.short_avgdelta); - } else { - mGlucoseStatus.put("delta", glucoseStatus.delta); - } - mGlucoseStatus.put("short_avgdelta", glucoseStatus.short_avgdelta); - mGlucoseStatus.put("long_avgdelta", glucoseStatus.long_avgdelta); - mGlucoseStatus.put("date", glucoseStatus.date); - - mMealData = new JSONObject(); - mMealData.put("carbs", mealData.carbs); - mMealData.put("boluses", mealData.boluses); - mMealData.put("mealCOB", mealData.mealCOB); - mMealData.put("slopeFromMaxDeviation", mealData.slopeFromMaxDeviation); - mMealData.put("slopeFromMinDeviation", mealData.slopeFromMinDeviation); - mMealData.put("lastBolusTime", mealData.lastBolusTime); - mMealData.put("lastCarbTime", mealData.lastCarbTime); - - - if (constraintChecker.isAutosensModeEnabled().value()) { - mAutosensData = new JSONObject(); - mAutosensData.put("ratio", autosensDataRatio); - } else { - mAutosensData = new JSONObject(); - mAutosensData.put("ratio", 1.0); - } - mMicrobolusAllowed = microBolusAllowed; - mSMBAlwaysAllowed = advancedFiltering; - - mCurrentTime = now; - - mIsSaveCgmSource = isSaveCgmSource; - } - - private Object makeParam(JSONObject jsonObject, Context rhino, Scriptable scope) { - - if (jsonObject == null) return Undefined.instance; - - return NativeJSON.parse(rhino, scope, jsonObject.toString(), (context, scriptable, scriptable1, objects) -> objects[1]); - } - - private Object makeParamArray(JSONArray jsonArray, Context rhino, Scriptable scope) { - //Object param = NativeJSON.parse(rhino, scope, "{myarray: " + jsonArray.toString() + " }", new Callable() { - return NativeJSON.parse(rhino, scope, jsonArray.toString(), (context, scriptable, scriptable1, objects) -> objects[1]); - } - - private String readFile(String filename) throws IOException { - byte[] bytes = mScriptReader.readFile(filename); - String string = new String(bytes, StandardCharsets.UTF_8); - if (string.startsWith("#!/usr/bin/env node")) { - string = string.substring(20); - } - return string; - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.kt new file mode 100644 index 0000000000..13a11fa774 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.kt @@ -0,0 +1,290 @@ +package info.nightscout.androidaps.plugins.aps.openAPSSMB + +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.Constants +import info.nightscout.androidaps.R +import info.nightscout.androidaps.data.IobTotal +import info.nightscout.androidaps.data.MealData +import info.nightscout.androidaps.data.Profile +import info.nightscout.androidaps.interfaces.ActivePluginProvider +import info.nightscout.androidaps.interfaces.ProfileFunction +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.aps.logger.LoggerCallback +import info.nightscout.androidaps.plugins.aps.loop.ScriptReader +import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker +import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansUploader +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.utils.SafeParse +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.sharedPreferences.SP +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import org.mozilla.javascript.* +import org.mozilla.javascript.Function +import java.io.IOException +import java.lang.reflect.InvocationTargetException +import java.nio.charset.StandardCharsets +import javax.inject.Inject + +class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader: ScriptReader, private val injector: HasAndroidInjector) { + + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var constraintChecker: ConstraintChecker + @Inject lateinit var sp: SP + @Inject lateinit var resourceHelper: ResourceHelper + @Inject lateinit var profileFunction: ProfileFunction + @Inject lateinit var treatmentsPlugin: TreatmentsPlugin + @Inject lateinit var activePluginProvider: ActivePluginProvider + @Inject lateinit var openHumansUploader: OpenHumansUploader + + private var profile = JSONObject() + private var mGlucoseStatus = JSONObject() + private var iobData: JSONArray? = null + private var mealData = JSONObject() + private var currentTemp = JSONObject() + private var autosensData = JSONObject() + private var microBolusAllowed = false + private var smbAlwaysAllowed = false + private var currentTime: Long = 0 + private var saveCgmSource = false + var currentTempParam: String? = null + private set + var iobDataParam: String? = null + private set + var glucoseStatusParam: String? = null + private set + var profileParam: String? = null + private set + var mealDataParam: String? = null + private set + var scriptDebug = "" + private set + + @Suppress("SpellCheckingInspection") + operator fun invoke(): DetermineBasalResultSMB? { + aapsLogger.debug(LTag.APS, ">>> Invoking determine_basal <<<") + aapsLogger.debug(LTag.APS, "Glucose status: " + mGlucoseStatus.toString().also { glucoseStatusParam = it }) + aapsLogger.debug(LTag.APS, "IOB data: " + iobData.toString().also { iobDataParam = it }) + aapsLogger.debug(LTag.APS, "Current temp: " + currentTemp.toString().also { currentTempParam = it }) + aapsLogger.debug(LTag.APS, "Profile: " + profile.toString().also { profileParam = it }) + aapsLogger.debug(LTag.APS, "Meal data: " + mealData.toString().also { mealDataParam = it }) + aapsLogger.debug(LTag.APS, "Autosens data: $autosensData") + aapsLogger.debug(LTag.APS, "Reservoir data: " + "undefined") + aapsLogger.debug(LTag.APS, "MicroBolusAllowed: $microBolusAllowed") + aapsLogger.debug(LTag.APS, "SMBAlwaysAllowed: $smbAlwaysAllowed") + aapsLogger.debug(LTag.APS, "CurrentTime: $currentTime") + aapsLogger.debug(LTag.APS, "isSaveCgmSource: $saveCgmSource") + var determineBasalResultSMB: DetermineBasalResultSMB? = null + val rhino = Context.enter() + val scope: Scriptable = rhino.initStandardObjects() + // Turn off optimization to make Rhino Android compatible + rhino.optimizationLevel = -1 + try { + + //register logger callback for console.log and console.error + ScriptableObject.defineClass(scope, LoggerCallback::class.java) + val myLogger = rhino.newObject(scope, "LoggerCallback", null) + scope.put("console2", scope, myLogger) + rhino.evaluateString(scope, readFile("OpenAPSAMA/loggerhelper.js"), "JavaScript", 0, null) + + //set module parent + rhino.evaluateString(scope, "var module = {\"parent\":Boolean(1)};", "JavaScript", 0, null) + rhino.evaluateString(scope, "var round_basal = function round_basal(basal, profile) { return basal; };", "JavaScript", 0, null) + rhino.evaluateString(scope, "require = function() {return round_basal;};", "JavaScript", 0, null) + + //generate functions "determine_basal" and "setTempBasal" + rhino.evaluateString(scope, readFile("OpenAPSSMB/determine-basal.js"), "JavaScript", 0, null) + rhino.evaluateString(scope, readFile("OpenAPSSMB/basal-set-temp.js"), "setTempBasal.js", 0, null) + val determineBasalObj = scope["determine_basal", scope] + val setTempBasalFunctionsObj = scope["tempBasalFunctions", scope] + + //call determine-basal + if (determineBasalObj is Function && setTempBasalFunctionsObj is NativeObject) { + + //prepare parameters + val params = arrayOf( + makeParam(mGlucoseStatus, rhino, scope), + makeParam(currentTemp, rhino, scope), + makeParamArray(iobData, rhino, scope), + makeParam(profile, rhino, scope), + makeParam(autosensData, rhino, scope), + makeParam(mealData, rhino, scope), + setTempBasalFunctionsObj, + java.lang.Boolean.valueOf(microBolusAllowed), + makeParam(null, rhino, scope), // reservoir data as undefined + java.lang.Long.valueOf(currentTime), + java.lang.Boolean.valueOf(saveCgmSource) + ) + val jsResult = determineBasalObj.call(rhino, scope, scope, params) as NativeObject + scriptDebug = LoggerCallback.scriptDebug + + // Parse the jsResult object to a JSON-String + val result = NativeJSON.stringify(rhino, scope, jsResult, null, null).toString() + aapsLogger.debug(LTag.APS, "Result: $result") + try { + val resultJson = JSONObject(result) + openHumansUploader.enqueueSMBData(profile, mGlucoseStatus, iobData, mealData, currentTemp, autosensData, microBolusAllowed, smbAlwaysAllowed, resultJson) + determineBasalResultSMB = DetermineBasalResultSMB(injector, resultJson) + } catch (e: JSONException) { + aapsLogger.error(LTag.APS, "Unhandled exception", e) + } + } else { + aapsLogger.error(LTag.APS, "Problem loading JS Functions") + } + } catch (e: IOException) { + aapsLogger.error(LTag.APS, "IOException") + } catch (e: RhinoException) { + aapsLogger.error(LTag.APS, "RhinoException: (" + e.lineNumber() + "," + e.columnNumber() + ") " + e.toString()) + } catch (e: IllegalAccessException) { + aapsLogger.error(LTag.APS, e.toString()) + } catch (e: InstantiationException) { + aapsLogger.error(LTag.APS, e.toString()) + } catch (e: InvocationTargetException) { + aapsLogger.error(LTag.APS, e.toString()) + } finally { + Context.exit() + } + glucoseStatusParam = mGlucoseStatus.toString() + iobDataParam = iobData.toString() + currentTempParam = currentTemp.toString() + profileParam = profile.toString() + mealDataParam = mealData.toString() + return determineBasalResultSMB + } + + @Suppress("SpellCheckingInspection") fun setData(profile: Profile, + maxIob: Double, + maxBasal: Double, + minBg: Double, + maxBg: Double, + targetBg: Double, + basalRate: Double, + iobArray: Array, + glucoseStatus: GlucoseStatus, + mealData: MealData, + autosensDataRatio: Double, + tempTargetSet: Boolean, + microBolusAllowed: Boolean, + uamAllowed: Boolean, + advancedFiltering: Boolean, + isSaveCgmSource: Boolean + ) { + val pump = activePluginProvider.activePump + val pumpBolusStep = pump.pumpDescription.bolusStep + this.profile.put("max_iob", maxIob) + //mProfile.put("dia", profile.getDia()); + this.profile.put("type", "current") + this.profile.put("max_daily_basal", profile.maxDailyBasal) + this.profile.put("max_basal", maxBasal) + this.profile.put("min_bg", minBg) + this.profile.put("max_bg", maxBg) + this.profile.put("target_bg", targetBg) + this.profile.put("carb_ratio", profile.ic) + this.profile.put("sens", profile.isfMgdl) + this.profile.put("max_daily_safety_multiplier", sp.getInt(R.string.key_openapsama_max_daily_safety_multiplier, 3)) + this.profile.put("current_basal_safety_multiplier", sp.getDouble(R.string.key_openapsama_current_basal_safety_multiplier, 4.0)) + + //mProfile.put("high_temptarget_raises_sensitivity", SP.getBoolean(R.string.key_high_temptarget_raises_sensitivity, SMBDefaults.high_temptarget_raises_sensitivity)); + this.profile.put("high_temptarget_raises_sensitivity", false) + //mProfile.put("low_temptarget_lowers_sensitivity", SP.getBoolean(R.string.key_low_temptarget_lowers_sensitivity, SMBDefaults.low_temptarget_lowers_sensitivity)); + this.profile.put("low_temptarget_lowers_sensitivity", false) + this.profile.put("sensitivity_raises_target", sp.getBoolean(R.string.key_sensitivity_raises_target, SMBDefaults.sensitivity_raises_target)) + this.profile.put("resistance_lowers_target", sp.getBoolean(R.string.key_resistance_lowers_target, SMBDefaults.resistance_lowers_target)) + this.profile.put("adv_target_adjustments", SMBDefaults.adv_target_adjustments) + this.profile.put("exercise_mode", SMBDefaults.exercise_mode) + this.profile.put("half_basal_exercise_target", SMBDefaults.half_basal_exercise_target) + this.profile.put("maxCOB", SMBDefaults.maxCOB) + this.profile.put("skip_neutral_temps", pump.setNeutralTempAtFullHour()) + // min_5m_carbimpact is not used within SMB determinebasal + //if (mealData.usedMinCarbsImpact > 0) { + // mProfile.put("min_5m_carbimpact", mealData.usedMinCarbsImpact); + //} else { + // mProfile.put("min_5m_carbimpact", SP.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact)); + //} + this.profile.put("remainingCarbsCap", SMBDefaults.remainingCarbsCap) + this.profile.put("enableUAM", uamAllowed) + this.profile.put("A52_risk_enable", SMBDefaults.A52_risk_enable) + val smbEnabled = sp.getBoolean(R.string.key_use_smb, false) + this.profile.put("SMBInterval", sp.getInt(R.string.key_smbinterval, SMBDefaults.SMBInterval)) + this.profile.put("enableSMB_with_COB", smbEnabled && sp.getBoolean(R.string.key_enableSMB_with_COB, false)) + this.profile.put("enableSMB_with_temptarget", smbEnabled && sp.getBoolean(R.string.key_enableSMB_with_temptarget, false)) + this.profile.put("allowSMB_with_high_temptarget", smbEnabled && sp.getBoolean(R.string.key_allowSMB_with_high_temptarget, false)) + this.profile.put("enableSMB_always", smbEnabled && sp.getBoolean(R.string.key_enableSMB_always, false) && advancedFiltering) + this.profile.put("enableSMB_after_carbs", smbEnabled && sp.getBoolean(R.string.key_enableSMB_after_carbs, false) && advancedFiltering) + this.profile.put("maxSMBBasalMinutes", sp.getInt(R.string.key_smbmaxminutes, SMBDefaults.maxSMBBasalMinutes)) + this.profile.put("maxUAMSMBBasalMinutes", sp.getInt(R.string.key_uamsmbmaxminutes, SMBDefaults.maxUAMSMBBasalMinutes)) + //set the min SMB amount to be the amount set by the pump. + this.profile.put("bolus_increment", pumpBolusStep) + this.profile.put("carbsReqThreshold", sp.getInt(R.string.key_carbsReqThreshold, SMBDefaults.carbsReqThreshold)) + this.profile.put("current_basal", basalRate) + this.profile.put("temptargetSet", tempTargetSet) + this.profile.put("autosens_max", SafeParse.stringToDouble(sp.getString(R.string.key_openapsama_autosens_max, "1.2"))) + if (profileFunction.getUnits() == Constants.MMOL) { + this.profile.put("out_units", "mmol/L") + } + val now = System.currentTimeMillis() + val tb = treatmentsPlugin.getTempBasalFromHistory(now) + currentTemp.put("temp", "absolute") + currentTemp.put("duration", tb?.plannedRemainingMinutes ?: 0) + currentTemp.put("rate", tb?.tempBasalConvertedToAbsolute(now, profile) ?: 0.0) + + // as we have non default temps longer than 30 mintues + val tempBasal = treatmentsPlugin.getTempBasalFromHistory(System.currentTimeMillis()) + if (tempBasal != null) { + currentTemp.put("minutesrunning", tempBasal.realDuration) + } + iobData = IobCobCalculatorPlugin.convertToJSONArray(iobArray) + mGlucoseStatus.put("glucose", glucoseStatus.glucose) + mGlucoseStatus.put("noise", glucoseStatus.noise) + if (sp.getBoolean(R.string.key_always_use_shortavg, false)) { + mGlucoseStatus.put("delta", glucoseStatus.short_avgdelta) + } else { + mGlucoseStatus.put("delta", glucoseStatus.delta) + } + mGlucoseStatus.put("short_avgdelta", glucoseStatus.short_avgdelta) + mGlucoseStatus.put("long_avgdelta", glucoseStatus.long_avgdelta) + mGlucoseStatus.put("date", glucoseStatus.date) + this.mealData.put("carbs", mealData.carbs) + this.mealData.put("boluses", mealData.boluses) + this.mealData.put("mealCOB", mealData.mealCOB) + this.mealData.put("slopeFromMaxDeviation", mealData.slopeFromMaxDeviation) + this.mealData.put("slopeFromMinDeviation", mealData.slopeFromMinDeviation) + this.mealData.put("lastBolusTime", mealData.lastBolusTime) + this.mealData.put("lastCarbTime", mealData.lastCarbTime) + if (constraintChecker.isAutosensModeEnabled().value()) { + autosensData.put("ratio", autosensDataRatio) + } else { + autosensData.put("ratio", 1.0) + } + this.microBolusAllowed = microBolusAllowed + smbAlwaysAllowed = advancedFiltering + currentTime = now + saveCgmSource = isSaveCgmSource + } + + private fun makeParam(jsonObject: JSONObject?, rhino: Context, scope: Scriptable): Any { + return if (jsonObject == null) Undefined.instance + else NativeJSON.parse(rhino, scope, jsonObject.toString()) { _: Context?, _: Scriptable?, _: Scriptable?, objects: Array -> objects[1] } + } + + private fun makeParamArray(jsonArray: JSONArray?, rhino: Context, scope: Scriptable): Any { + return NativeJSON.parse(rhino, scope, jsonArray.toString()) { _: Context?, _: Scriptable?, _: Scriptable?, objects: Array -> objects[1] } + } + + @Throws(IOException::class) private fun readFile(filename: String): String { + val bytes = scriptReader.readFile(filename) + var string = String(bytes, StandardCharsets.UTF_8) + if (string.startsWith("#!/usr/bin/env node")) { + string = string.substring(20) + } + return string + } + + init { + injector.androidInjector().inject(this) + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBFragment.kt index 9cb068f0d6..be4e1015fa 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBFragment.kt @@ -117,7 +117,7 @@ class OpenAPSSMBFragment : DaggerFragment() { if (openAPSSMBPlugin.lastAPSRun != 0L) { binding.lastrun.text = dateUtil.dateAndTimeString(openAPSSMBPlugin.lastAPSRun) } - openAPSSMBPlugin.lastAutosensResult?.let { + openAPSSMBPlugin.lastAutosensResult.let { binding.autosensdata.text = JSONFormatter.format(it.json()) } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.java deleted file mode 100644 index 4ddbcdca76..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.java +++ /dev/null @@ -1,323 +0,0 @@ -package info.nightscout.androidaps.plugins.aps.openAPSSMB; - -import android.content.Context; - -import androidx.preference.PreferenceFragmentCompat; -import androidx.preference.SwitchPreference; - -import org.jetbrains.annotations.NotNull; -import org.json.JSONException; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import dagger.android.HasAndroidInjector; -import info.nightscout.androidaps.R; -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.ActivePluginProvider; -import info.nightscout.androidaps.interfaces.Constraint; -import info.nightscout.androidaps.interfaces.ConstraintsInterface; -import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.interfaces.PluginDescription; -import info.nightscout.androidaps.interfaces.PluginType; -import info.nightscout.androidaps.interfaces.ProfileFunction; -import info.nightscout.androidaps.interfaces.PumpInterface; -import info.nightscout.androidaps.logging.AAPSLogger; -import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateGui; -import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateResultGui; -import info.nightscout.androidaps.plugins.aps.loop.APSResult; -import info.nightscout.androidaps.plugins.aps.loop.ScriptReader; -import info.nightscout.androidaps.plugins.bus.RxBusWrapper; -import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData; -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.FabricPrivacy; -import info.nightscout.androidaps.utils.HardLimits; -import info.nightscout.androidaps.utils.Profiler; -import info.nightscout.androidaps.utils.Round; -import info.nightscout.androidaps.utils.resources.ResourceHelper; -import info.nightscout.androidaps.utils.sharedPreferences.SP; - -@Singleton -public class OpenAPSSMBPlugin extends PluginBase implements APSInterface, ConstraintsInterface { - private final ConstraintChecker constraintChecker; - private final ResourceHelper resourceHelper; - private final ProfileFunction profileFunction; - private final Context context; - private final RxBusWrapper rxBus; - private final ActivePluginProvider activePlugin; - private final TreatmentsPlugin treatmentsPlugin; - private final IobCobCalculatorPlugin iobCobCalculatorPlugin; - private final HardLimits hardLimits; - private final Profiler profiler; - private final FabricPrivacy fabricPrivacy; - private final SP sp; - - // last values - DetermineBasalAdapterSMBJS lastDetermineBasalAdapterSMBJS = null; - long lastAPSRun = 0; - DetermineBasalResultSMB lastAPSResult = null; - AutosensResult lastAutosensResult = null; - - @Inject - public OpenAPSSMBPlugin( - HasAndroidInjector injector, - AAPSLogger aapsLogger, - RxBusWrapper rxBus, - ConstraintChecker constraintChecker, - ResourceHelper resourceHelper, - ProfileFunction profileFunction, - Context context, - ActivePluginProvider activePlugin, - TreatmentsPlugin treatmentsPlugin, - IobCobCalculatorPlugin iobCobCalculatorPlugin, - HardLimits hardLimits, - Profiler profiler, - FabricPrivacy fabricPrivacy, - SP sp - ) { - super(new PluginDescription() - .mainType(PluginType.APS) - .fragmentClass(OpenAPSSMBFragment.class.getName()) - .pluginIcon(R.drawable.ic_generic_icon) - .pluginName(R.string.openapssmb) - .shortName(R.string.smb_shortname) - .preferencesId(R.xml.pref_openapssmb) - .description(R.string.description_smb) - .setDefault(), - aapsLogger, resourceHelper, injector - ); - this.constraintChecker = constraintChecker; - this.resourceHelper = resourceHelper; - this.profileFunction = profileFunction; - this.rxBus = rxBus; - this.context = context; - this.activePlugin = activePlugin; - this.treatmentsPlugin = treatmentsPlugin; - this.iobCobCalculatorPlugin = iobCobCalculatorPlugin; - this.hardLimits = hardLimits; - this.profiler = profiler; - this.fabricPrivacy = fabricPrivacy; - this.sp = sp; - } - - @Override - public boolean specialEnableCondition() { - try { - PumpInterface pump = activePlugin.getActivePump(); - return pump.getPumpDescription().isTempBasalCapable; - } catch (Exception ignored) { - // may fail during initialization - return true; - } - } - - @Override - public boolean specialShowInListCondition() { - PumpInterface pump = activePlugin.getActivePump(); - return pump.getPumpDescription().isTempBasalCapable; - } - - @Override - public void preprocessPreferences(@NotNull PreferenceFragmentCompat preferenceFragment) { - super.preprocessPreferences(preferenceFragment); - boolean smbAlwaysEnabled = sp.getBoolean(R.string.key_enableSMB_always, false); - - SwitchPreference withCOB = preferenceFragment.findPreference(resourceHelper.gs(R.string.key_enableSMB_with_COB)); - if (withCOB != null) { - withCOB.setVisible(!smbAlwaysEnabled); - } - SwitchPreference withTempTarget = preferenceFragment.findPreference(resourceHelper.gs(R.string.key_enableSMB_with_temptarget)); - if (withTempTarget != null) { - withTempTarget.setVisible(!smbAlwaysEnabled); - } - SwitchPreference afterCarbs = preferenceFragment.findPreference(resourceHelper.gs(R.string.key_enableSMB_after_carbs)); - if (afterCarbs != null) { - afterCarbs.setVisible(!smbAlwaysEnabled); - } - } - - @Override - public APSResult getLastAPSResult() { - return lastAPSResult; - } - - @Override - public long getLastAPSRun() { - return lastAPSRun; - } - - @Override - public void invoke(String initiator, boolean tempBasalFallback) { - getAapsLogger().debug(LTag.APS, "invoke from " + initiator + " tempBasalFallback: " + tempBasalFallback); - lastAPSResult = null; - DetermineBasalAdapterSMBJS determineBasalAdapterSMBJS; - determineBasalAdapterSMBJS = new DetermineBasalAdapterSMBJS(new ScriptReader(context), getInjector()); - - GlucoseStatus glucoseStatus = new GlucoseStatus(getInjector()).getGlucoseStatusData(); - Profile profile = profileFunction.getProfile(); - PumpInterface pump = activePlugin.getActivePump(); - - if (profile == null) { - rxBus.send(new EventOpenAPSUpdateResultGui(resourceHelper.gs(R.string.noprofileselected))); - getAapsLogger().debug(LTag.APS, resourceHelper.gs(R.string.noprofileselected)); - return; - } - - if (!isEnabled(PluginType.APS)) { - rxBus.send(new EventOpenAPSUpdateResultGui(resourceHelper.gs(R.string.openapsma_disabled))); - getAapsLogger().debug(LTag.APS, resourceHelper.gs(R.string.openapsma_disabled)); - return; - } - - if (glucoseStatus == null) { - rxBus.send(new EventOpenAPSUpdateResultGui(resourceHelper.gs(R.string.openapsma_noglucosedata))); - getAapsLogger().debug(LTag.APS, resourceHelper.gs(R.string.openapsma_noglucosedata)); - return; - } - - Constraint inputConstraints = new Constraint<>(0d); // fake. only for collecting all results - - Constraint maxBasalConstraint = constraintChecker.getMaxBasalAllowed(profile); - inputConstraints.copyReasons(maxBasalConstraint); - double maxBasal = maxBasalConstraint.value(); - double minBg = profile.getTargetLowMgdl(); - double maxBg = profile.getTargetHighMgdl(); - double targetBg = profile.getTargetMgdl(); - - minBg = Round.roundTo(minBg, 0.1d); - maxBg = Round.roundTo(maxBg, 0.1d); - - long start = System.currentTimeMillis(); - long startPart = System.currentTimeMillis(); - - MealData mealData = iobCobCalculatorPlugin.getMealData(); - profiler.log(LTag.APS, "getMealData()", startPart); - - Constraint maxIOBAllowedConstraint = constraintChecker.getMaxIOBAllowed(); - inputConstraints.copyReasons(maxIOBAllowedConstraint); - double maxIob = maxIOBAllowedConstraint.value(); - - minBg = hardLimits.verifyHardLimits(minBg, "minBg", hardLimits.getVERY_HARD_LIMIT_MIN_BG()[0], hardLimits.getVERY_HARD_LIMIT_MIN_BG()[1]); - maxBg = hardLimits.verifyHardLimits(maxBg, "maxBg", hardLimits.getVERY_HARD_LIMIT_MAX_BG()[0], hardLimits.getVERY_HARD_LIMIT_MAX_BG()[1]); - targetBg = hardLimits.verifyHardLimits(targetBg, "targetBg", hardLimits.getVERY_HARD_LIMIT_TARGET_BG()[0], hardLimits.getVERY_HARD_LIMIT_TARGET_BG()[1]); - - boolean isTempTarget = false; - TempTarget tempTarget = treatmentsPlugin.getTempTargetFromHistory(System.currentTimeMillis()); - if (tempTarget != null) { - isTempTarget = true; - minBg = hardLimits.verifyHardLimits(tempTarget.low, "minBg", hardLimits.getVERY_HARD_LIMIT_TEMP_MIN_BG()[0], hardLimits.getVERY_HARD_LIMIT_TEMP_MIN_BG()[1]); - maxBg = hardLimits.verifyHardLimits(tempTarget.high, "maxBg", hardLimits.getVERY_HARD_LIMIT_TEMP_MAX_BG()[0], hardLimits.getVERY_HARD_LIMIT_TEMP_MAX_BG()[1]); - targetBg = hardLimits.verifyHardLimits(tempTarget.target(), "targetBg", hardLimits.getVERY_HARD_LIMIT_TEMP_TARGET_BG()[0], hardLimits.getVERY_HARD_LIMIT_TEMP_TARGET_BG()[1]); - } - - - if (!hardLimits.checkOnlyHardLimits(profile.getDia(), "dia", hardLimits.minDia(), hardLimits.maxDia())) - return; - if (!hardLimits.checkOnlyHardLimits(profile.getIcTimeFromMidnight(Profile.secondsFromMidnight()), "carbratio", hardLimits.minIC(), hardLimits.maxIC())) - return; - if (!hardLimits.checkOnlyHardLimits(profile.getIsfMgdl(), "sens", hardLimits.getMINISF(), hardLimits.getMAXISF())) - return; - if (!hardLimits.checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.02, hardLimits.maxBasal())) - return; - if (!hardLimits.checkOnlyHardLimits(pump.getBaseBasalRate(), "current_basal", 0.01, hardLimits.maxBasal())) - return; - - startPart = System.currentTimeMillis(); - if (constraintChecker.isAutosensModeEnabled().value()) { - AutosensData autosensData = iobCobCalculatorPlugin.getLastAutosensDataSynchronized("OpenAPSPlugin"); - if (autosensData == null) { - rxBus.send(new EventOpenAPSUpdateResultGui(resourceHelper.gs(R.string.openaps_noasdata))); - return; - } - lastAutosensResult = autosensData.autosensResult; - } else { - lastAutosensResult = new AutosensResult(); - lastAutosensResult.sensResult = "autosens disabled"; - } - - IobTotal[] iobArray = iobCobCalculatorPlugin.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget); - profiler.log(LTag.APS, "calculateIobArrayInDia()", startPart); - - startPart = System.currentTimeMillis(); - Constraint smbAllowed = new Constraint<>(!tempBasalFallback); - constraintChecker.isSMBModeEnabled(smbAllowed); - inputConstraints.copyReasons(smbAllowed); - - Constraint advancedFiltering = new Constraint<>(!tempBasalFallback); - constraintChecker.isAdvancedFilteringEnabled(advancedFiltering); - inputConstraints.copyReasons(advancedFiltering); - - Constraint uam = new Constraint<>(true); - constraintChecker.isUAMEnabled(uam); - inputConstraints.copyReasons(uam); - - profiler.log(LTag.APS, "detectSensitivityandCarbAbsorption()", startPart); - profiler.log(LTag.APS, "SMB data gathering", start); - - start = System.currentTimeMillis(); - try { - determineBasalAdapterSMBJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg, activePlugin.getActivePump().getBaseBasalRate(), iobArray, glucoseStatus, mealData, - lastAutosensResult.ratio, //autosensDataRatio - isTempTarget, - smbAllowed.value(), - uam.value(), - advancedFiltering.value(), - activePlugin.getActiveBgSource().getClass().getSimpleName().equals("DexcomPlugin") - ); - } catch (JSONException e) { - fabricPrivacy.logException(e); - return; - } - - long now = System.currentTimeMillis(); - - DetermineBasalResultSMB determineBasalResultSMB = determineBasalAdapterSMBJS.invoke(); - profiler.log(LTag.APS, "SMB calculation", start); - if (determineBasalResultSMB == null) { - getAapsLogger().error(LTag.APS, "SMB calculation returned null"); - lastDetermineBasalAdapterSMBJS = null; - lastAPSResult = null; - lastAPSRun = 0; - } else { - // TODO still needed with oref1? - // Fix bug determine basal - if (determineBasalResultSMB.getRate() == 0d && determineBasalResultSMB.getDuration() == 0 && !treatmentsPlugin.isTempBasalInProgress()) - determineBasalResultSMB.setTempBasalRequested(false); - - determineBasalResultSMB.setIob(iobArray[0]); - - try { - determineBasalResultSMB.getJson().put("timestamp", DateUtil.toISOString(now)); - } catch (JSONException e) { - getAapsLogger().error(LTag.APS, "Unhandled exception", e); - } - - determineBasalResultSMB.setInputConstraints(inputConstraints); - - lastDetermineBasalAdapterSMBJS = determineBasalAdapterSMBJS; - lastAPSResult = determineBasalResultSMB; - lastAPSRun = now; - } - rxBus.send(new EventOpenAPSUpdateGui()); - - //deviceStatus.suggested = determineBasalResultAMA.json; - } - - @NotNull - @Override - public Constraint isSuperBolusEnabled(Constraint value) { - value.set(getAapsLogger(), false); - return value; - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.kt new file mode 100644 index 0000000000..f1245810f0 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.kt @@ -0,0 +1,202 @@ +package info.nightscout.androidaps.plugins.aps.openAPSSMB + +import android.content.Context +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.SwitchPreference +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.R +import info.nightscout.androidaps.data.Profile +import info.nightscout.androidaps.interfaces.* +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateGui +import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateResultGui +import info.nightscout.androidaps.plugins.aps.loop.ScriptReader +import info.nightscout.androidaps.plugins.bus.RxBusWrapper +import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.HardLimits +import info.nightscout.androidaps.utils.Profiler +import info.nightscout.androidaps.utils.Round +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.sharedPreferences.SP +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +open class OpenAPSSMBPlugin @Inject constructor( + injector: HasAndroidInjector, + aapsLogger: AAPSLogger, + private val rxBus: RxBusWrapper, + private val constraintChecker: ConstraintChecker, + resourceHelper: ResourceHelper, + private val profileFunction: ProfileFunction, + private val context: Context, + private val activePlugin: ActivePluginProvider, + private val treatmentsPlugin: TreatmentsPlugin, + private val iobCobCalculatorPlugin: IobCobCalculatorPlugin, + private val hardLimits: HardLimits, + private val profiler: Profiler, + private val sp: SP +) : PluginBase(PluginDescription() + .mainType(PluginType.APS) + .fragmentClass(OpenAPSSMBFragment::class.java.name) + .pluginIcon(R.drawable.ic_generic_icon) + .pluginName(R.string.openapssmb) + .shortName(R.string.smb_shortname) + .preferencesId(R.xml.pref_openapssmb) + .description(R.string.description_smb) + .setDefault(), + aapsLogger, resourceHelper, injector +), APSInterface, ConstraintsInterface { + + // last values + override var lastAPSRun: Long = 0 + override var lastAPSResult: DetermineBasalResultSMB? = null + var lastDetermineBasalAdapterSMBJS: DetermineBasalAdapterSMBJS? = null + var lastAutosensResult = AutosensResult() + + override fun specialEnableCondition(): Boolean { + return try { + activePlugin.activePump.pumpDescription.isTempBasalCapable + } catch (ignored: Exception) { + // may fail during initialization + true + } + } + + override fun specialShowInListCondition(): Boolean { + val pump = activePlugin.activePump + return pump.pumpDescription.isTempBasalCapable + } + + override fun preprocessPreferences(preferenceFragment: PreferenceFragmentCompat) { + super.preprocessPreferences(preferenceFragment) + val smbAlwaysEnabled = sp.getBoolean(R.string.key_enableSMB_always, false) + preferenceFragment.findPreference(resourceHelper.gs(R.string.key_enableSMB_with_COB))?.isVisible = !smbAlwaysEnabled + preferenceFragment.findPreference(resourceHelper.gs(R.string.key_enableSMB_with_temptarget))?.isVisible = !smbAlwaysEnabled + preferenceFragment.findPreference(resourceHelper.gs(R.string.key_enableSMB_after_carbs))?.isVisible = !smbAlwaysEnabled + } + + override fun invoke(initiator: String, tempBasalFallback: Boolean) { + aapsLogger.debug(LTag.APS, "invoke from $initiator tempBasalFallback: $tempBasalFallback") + lastAPSResult = null + val glucoseStatus = GlucoseStatus(injector).glucoseStatusData + val profile = profileFunction.getProfile() + val pump = activePlugin.activePump + if (profile == null) { + rxBus.send(EventOpenAPSUpdateResultGui(resourceHelper.gs(R.string.noprofileselected))) + aapsLogger.debug(LTag.APS, resourceHelper.gs(R.string.noprofileselected)) + return + } + if (!isEnabled(PluginType.APS)) { + rxBus.send(EventOpenAPSUpdateResultGui(resourceHelper.gs(R.string.openapsma_disabled))) + aapsLogger.debug(LTag.APS, resourceHelper.gs(R.string.openapsma_disabled)) + return + } + if (glucoseStatus == null) { + rxBus.send(EventOpenAPSUpdateResultGui(resourceHelper.gs(R.string.openapsma_noglucosedata))) + aapsLogger.debug(LTag.APS, resourceHelper.gs(R.string.openapsma_noglucosedata)) + return + } + + val inputConstraints = Constraint(0.0) // fake. only for collecting all results + val maxBasal = constraintChecker.getMaxBasalAllowed(profile).also { + inputConstraints.copyReasons(it) + }.value() + var start = System.currentTimeMillis() + var startPart = System.currentTimeMillis() + profiler.log(LTag.APS, "getMealData()", startPart) + val maxIob = constraintChecker.getMaxIOBAllowed().also { maxIOBAllowedConstraint -> + inputConstraints.copyReasons(maxIOBAllowedConstraint) + }.value() + + var minBg = hardLimits.verifyHardLimits(Round.roundTo(profile.targetLowMgdl, 0.1), "minBg", hardLimits.VERY_HARD_LIMIT_MIN_BG[0].toDouble(), hardLimits.VERY_HARD_LIMIT_MIN_BG[1].toDouble()) + var maxBg = hardLimits.verifyHardLimits(Round.roundTo(profile.targetHighMgdl, 0.1), "maxBg", hardLimits.VERY_HARD_LIMIT_MAX_BG[0].toDouble(), hardLimits.VERY_HARD_LIMIT_MAX_BG[1].toDouble()) + var targetBg = hardLimits.verifyHardLimits(profile.targetMgdl, "targetBg", hardLimits.VERY_HARD_LIMIT_TARGET_BG[0].toDouble(), hardLimits.VERY_HARD_LIMIT_TARGET_BG[1].toDouble()) + var isTempTarget = false + treatmentsPlugin.getTempTargetFromHistory(System.currentTimeMillis())?.let { tempTarget -> + isTempTarget = true + minBg = hardLimits.verifyHardLimits(tempTarget.low, "minBg", hardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[0].toDouble(), hardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[1].toDouble()) + maxBg = hardLimits.verifyHardLimits(tempTarget.high, "maxBg", hardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0].toDouble(), hardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1].toDouble()) + targetBg = hardLimits.verifyHardLimits(tempTarget.target(), "targetBg", hardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[0].toDouble(), hardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[1].toDouble()) + } + if (!hardLimits.checkOnlyHardLimits(profile.dia, "dia", hardLimits.minDia(), hardLimits.maxDia())) return + if (!hardLimits.checkOnlyHardLimits(profile.getIcTimeFromMidnight(Profile.secondsFromMidnight()), "carbratio", hardLimits.minIC(), hardLimits.maxIC())) return + if (!hardLimits.checkOnlyHardLimits(profile.isfMgdl, "sens", hardLimits.MINISF, hardLimits.MAXISF)) return + if (!hardLimits.checkOnlyHardLimits(profile.maxDailyBasal, "max_daily_basal", 0.02, hardLimits.maxBasal())) return + if (!hardLimits.checkOnlyHardLimits(pump.baseBasalRate, "current_basal", 0.01, hardLimits.maxBasal())) return + startPart = System.currentTimeMillis() + if (constraintChecker.isAutosensModeEnabled().value()) { + val autosensData = iobCobCalculatorPlugin.getLastAutosensDataSynchronized("OpenAPSPlugin") + if (autosensData == null) { + rxBus.send(EventOpenAPSUpdateResultGui(resourceHelper.gs(R.string.openaps_noasdata))) + return + } + lastAutosensResult = autosensData.autosensResult + } else { + lastAutosensResult.sensResult = "autosens disabled" + } + val iobArray = iobCobCalculatorPlugin.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget) + profiler.log(LTag.APS, "calculateIobArrayInDia()", startPart) + startPart = System.currentTimeMillis() + val smbAllowed = Constraint(!tempBasalFallback).also { + constraintChecker.isSMBModeEnabled(it) + inputConstraints.copyReasons(it) + } + val advancedFiltering = Constraint(!tempBasalFallback).also { + constraintChecker.isAdvancedFilteringEnabled(it) + inputConstraints.copyReasons(it) + } + val uam = Constraint(true).also { + constraintChecker.isUAMEnabled(it) + inputConstraints.copyReasons(it) + } + profiler.log(LTag.APS, "detectSensitivityAndCarbAbsorption()", startPart) + profiler.log(LTag.APS, "SMB data gathering", start) + start = System.currentTimeMillis() + + DetermineBasalAdapterSMBJS(ScriptReader(context), injector).also { determineBasalAdapterSMBJS -> + determineBasalAdapterSMBJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg, + activePlugin.activePump.baseBasalRate, + iobArray, + glucoseStatus, + iobCobCalculatorPlugin.mealData, + lastAutosensResult.ratio, + isTempTarget, + smbAllowed.value(), + uam.value(), + advancedFiltering.value(), + activePlugin.activeBgSource.javaClass.simpleName == "DexcomPlugin") + val now = System.currentTimeMillis() + val determineBasalResultSMB = determineBasalAdapterSMBJS.invoke() + profiler.log(LTag.APS, "SMB calculation", start) + if (determineBasalResultSMB == null) { + aapsLogger.error(LTag.APS, "SMB calculation returned null") + lastDetermineBasalAdapterSMBJS = null + lastAPSResult = null + lastAPSRun = 0 + } else { + // TODO still needed with oref1? + // Fix bug determine basal + if (determineBasalResultSMB.rate == 0.0 && determineBasalResultSMB.duration == 0 && !treatmentsPlugin.isTempBasalInProgress) determineBasalResultSMB.tempBasalRequested = false + determineBasalResultSMB.iob = iobArray[0] + determineBasalResultSMB.json?.put("timestamp", DateUtil.toISOString(now)) + determineBasalResultSMB.inputConstraints = inputConstraints + lastDetermineBasalAdapterSMBJS = determineBasalAdapterSMBJS + lastAPSResult = determineBasalResultSMB + lastAPSRun = now + } + } + rxBus.send(EventOpenAPSUpdateGui()) + } + + override fun isSuperBolusEnabled(value: Constraint): Constraint { + value[aapsLogger] = false + return value + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt index 507e2cd1eb..63c1719a45 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt @@ -196,19 +196,19 @@ class ObjectivesFragment : DaggerFragment() { if (task.shouldBeIgnored()) continue // name val name = TextView(holder.binding.progress.context) - name.text = resourceHelper.gs(task.task) + ":" + name.text = "${resourceHelper.gs(task.task)}:" name.setTextColor(-0x1) holder.binding.progress.addView(name, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT) // hint task.hints.forEach { h -> - if (!task.isCompleted) - holder.binding.progress.addView(h.generate(context)) + if (!task.isCompleted()) + context?.let { holder.binding.progress.addView(h.generate(it)) } } // state val state = TextView(holder.binding.progress.context) state.setTextColor(-0x1) val basicHTML = "%2\$s" - val formattedHTML = String.format(basicHTML, if (task.isCompleted) "#4CAF50" else "#FF9800", task.progress) + val formattedHTML = String.format(basicHTML, if (task.isCompleted()) "#4CAF50" else "#FF9800", task.progress) state.text = HtmlHelper.fromHtml(formattedHTML) state.gravity = Gravity.END holder.binding.progress.addView(state, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT) @@ -307,7 +307,7 @@ class ObjectivesFragment : DaggerFragment() { holder.binding.unstart.setOnClickListener { activity?.let { activity -> OKDialog.showConfirmation(activity, resourceHelper.gs(R.string.objectives), resourceHelper.gs(R.string.doyouwantresetstart), Runnable { - uel.log("OBJECTVE UNSTARTED", i1 = position + 1) + uel.log("OBJECTIVE UNSTARTED", i1 = position + 1) objective.startedOn = 0 scrollToCurrentObjective() rxBus.send(EventObjectivesUpdateGui()) @@ -332,7 +332,7 @@ class ObjectivesFragment : DaggerFragment() { holder.binding.inputhint.visibility = View.VISIBLE holder.binding.enterbutton.setOnClickListener { val input = holder.binding.input.text.toString() - objective.specialAction(activity, input) + activity?.let { activity -> objective.specialAction(activity, input) } rxBus.send(EventObjectivesUpdateGui()) } } else { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/activities/ObjectivesExamDialog.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/activities/ObjectivesExamDialog.kt index 97da62bbac..c45d61bfd4 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/activities/ObjectivesExamDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/activities/ObjectivesExamDialog.kt @@ -80,28 +80,30 @@ class ObjectivesExamDialog : DaggerDialogFragment() { // Options binding.examOptions.removeAllViews() task.options.forEach { - val cb = it.generate(context) - if (task.answered) { - cb.isEnabled = false - if (it.isCorrect) - cb.isChecked = true + context?.let { context -> + val cb = it.generate(context) + if (task.answered) { + cb.isEnabled = false + if (it.isCorrect) + cb.isChecked = true + } + binding.examOptions.addView(cb) } - binding.examOptions.addView(cb) } // Hints binding.examHints.removeAllViews() for (h in task.hints) { - binding.examHints.addView(h.generate(context)) + context?.let { binding.examHints.addView(h.generate(it)) } } // Disabled to binding.examDisabledto.text = resourceHelper.gs(R.string.answerdisabledto, dateUtil.timeString(task.disabledTo)) - binding.examDisabledto.visibility = if (task.isEnabledAnswer) View.GONE else View.VISIBLE + binding.examDisabledto.visibility = if (task.isEnabledAnswer()) View.GONE else View.VISIBLE // Buttons - binding.examVerify.isEnabled = !task.answered && task.isEnabledAnswer + binding.examVerify.isEnabled = !task.answered && task.isEnabledAnswer() binding.examVerify.setOnClickListener { var result = true for (o in task.options) { - val option: Option = o as Option + val option: Option = o result = result && option.evaluate() } task.answered = result @@ -133,14 +135,14 @@ class ObjectivesExamDialog : DaggerDialogFragment() { binding.nextUnansweredButton.isEnabled = !objective.isCompleted binding.nextUnansweredButton.setOnClickListener { for (i in (currentTask + 1) until objective.tasks.size) { - if (!objective.tasks[i].isCompleted) { + if (!objective.tasks[i].isCompleted()) { currentTask = i updateGui() return@setOnClickListener } } for (i in 0..currentTask) { - if (!objective.tasks[i].isCompleted) { + if (!objective.tasks[i].isCompleted()) { currentTask = i updateGui() return@setOnClickListener diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective.java b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective.java deleted file mode 100644 index 95942c4ed2..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective.java +++ /dev/null @@ -1,295 +0,0 @@ -package info.nightscout.androidaps.plugins.constraints.objectives.objectives; - -import android.content.Context; -import android.graphics.Color; -import android.text.util.Linkify; -import android.widget.CheckBox; -import android.widget.TextView; - -import androidx.annotation.StringRes; -import androidx.fragment.app.FragmentActivity; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -import dagger.android.HasAndroidInjector; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.T; -import info.nightscout.androidaps.utils.resources.ResourceHelper; -import info.nightscout.androidaps.utils.sharedPreferences.SP; - -public abstract class Objective { - @Inject public SP sp; - @Inject public ResourceHelper resourceHelper; - - private final String spName; - @StringRes private final int objective; - @StringRes private final int gate; - private long startedOn; - private long accomplishedOn; - List tasks = new ArrayList<>(); - public boolean hasSpecialInput = false; - - public Objective(HasAndroidInjector injector, String spName, @StringRes int objective, @StringRes int gate) { - injector.androidInjector().inject(this); - this.spName = spName; - this.objective = objective; - this.gate = gate; - startedOn = sp.getLong("Objectives_" + spName + "_started", 0L); - accomplishedOn = sp.getLong("Objectives_" + spName + "_accomplished", 0L); - if ((accomplishedOn - DateUtil.now()) > T.hours(3).msecs() || (startedOn - DateUtil.now()) > T.hours(3).msecs()) { // more than 3 hours in the future - startedOn = 0; - accomplishedOn = 0; - } - setupTasks(tasks); - for (Task task : tasks) task.objective = this; - } - - public boolean isCompleted() { - for (Task task : tasks) { - if (!task.shouldBeIgnored() && !task.isCompleted()) - return false; - } - return true; - } - - public boolean isCompleted(long trueTime) { - for (Task task : tasks) { - if (!task.shouldBeIgnored() && !task.isCompleted(trueTime)) - return false; - } - return true; - } - - public boolean isAccomplished() { - return accomplishedOn != 0 && accomplishedOn < DateUtil.now(); - } - - public boolean isStarted() { - return startedOn != 0; - } - - public long getStartedOn() { - return startedOn; - } - - public int getObjective() { - return objective; - } - - public int getGate() { - return gate; - } - - public void setStartedOn(long startedOn) { - this.startedOn = startedOn; - sp.putLong("Objectives_" + spName + "_started", startedOn); - } - - public void setAccomplishedOn(long accomplishedOn) { - this.accomplishedOn = accomplishedOn; - sp.putLong("Objectives_" + spName + "_accomplished", accomplishedOn); - } - - public long getAccomplishedOn() { - return accomplishedOn; - } - - protected void setupTasks(List tasks) { - - } - - public List getTasks() { - return tasks; - } - - public boolean specialActionEnabled() { - return true; - } - - public void specialAction(FragmentActivity activity, String input) { - } - - public abstract class Task { - @StringRes - private final int task; - private Objective objective; - ArrayList hints = new ArrayList<>(); - - public Task(@StringRes int task) { - this.task = task; - } - - public @StringRes int getTask() { - return task; - } - - protected Objective getObjective() { - return objective; - } - - public abstract boolean isCompleted(); - - public boolean isCompleted(long trueTime) { - return isCompleted(); - } - - public String getProgress() { - return resourceHelper.gs(isCompleted() ? R.string.completed_well_done : R.string.not_completed_yet); - } - - Task hint(Hint hint) { - hints.add(hint); - return this; - } - - public ArrayList getHints() { - return hints; - } - - public boolean shouldBeIgnored() { - return false; - } - } - - public class MinimumDurationTask extends Task { - - private final long minimumDuration; - - MinimumDurationTask(long minimumDuration) { - super(R.string.time_elapsed); - this.minimumDuration = minimumDuration; - } - - @Override - public boolean isCompleted() { - return getObjective().isStarted() && System.currentTimeMillis() - getObjective().getStartedOn() >= minimumDuration; - } - - @Override - public boolean isCompleted(long trueTime) { - return getObjective().isStarted() && trueTime - getObjective().getStartedOn() >= minimumDuration; - } - - @Override - public String getProgress() { - return getDurationText(System.currentTimeMillis() - getObjective().getStartedOn()) - + " / " + getDurationText(minimumDuration); - } - - private String getDurationText(long duration) { - int days = (int) Math.floor((double) duration / T.days(1).msecs()); - int hours = (int) Math.floor((double) duration / T.hours(1).msecs()); - int minutes = (int) Math.floor((double) duration / T.mins(1).msecs()); - if (days > 0) return resourceHelper.gq(R.plurals.days, days, days); - else if (hours > 0) return resourceHelper.gq(R.plurals.hours, hours, hours); - else return resourceHelper.gq(R.plurals.minutes, minutes, minutes); - } - } - - public class ExamTask extends Task { - @StringRes - int question; - ArrayList