diff --git a/app/src/main/java/info/nightscout/androidaps/activities/ProfileHelperActivity.kt b/app/src/main/java/info/nightscout/androidaps/activities/ProfileHelperActivity.kt index 9950f0818a..8a9d37dc6e 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/ProfileHelperActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/ProfileHelperActivity.kt @@ -131,6 +131,7 @@ class ProfileHelperActivity : NoSplashAppCompatActivity() { // Default profile binding.copytolocalprofile.setOnClickListener { + storeValues() val age = ageUsed[tabSelected] val weight = weightUsed[tabSelected] val tdd = tddUsed[tabSelected] 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 484e445136..0e11aea05d 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/LoopDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/LoopDialog.kt @@ -22,7 +22,6 @@ import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker -import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin import info.nightscout.androidaps.queue.Callback import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.ToastUtils @@ -42,7 +41,6 @@ class LoopDialog : DaggerDialogFragment() { @Inject lateinit var resourceHelper: ResourceHelper @Inject lateinit var profileFunction: ProfileFunction @Inject lateinit var loopPlugin: LoopPlugin - @Inject lateinit var objectivesPlugin: ObjectivesPlugin @Inject lateinit var activePlugin: ActivePluginProvider @Inject lateinit var constraintChecker: ConstraintChecker @Inject lateinit var commandQueue: CommandQueueProvider @@ -107,7 +105,7 @@ class LoopDialog : DaggerDialogFragment() { binding.cancel.setOnClickListener { dismiss() } refreshDialog = Runnable { - scheduleUpdateGUI("refreshDialog") + scheduleUpdateGUI() loopHandler.postDelayed(refreshDialog, 15 * 1000L) } loopHandler.postDelayed(refreshDialog, 15 * 1000L) @@ -122,11 +120,11 @@ class LoopDialog : DaggerDialogFragment() { var task: Runnable? = null - private fun scheduleUpdateGUI(from: String) { + private fun scheduleUpdateGUI() { class UpdateRunnable : Runnable { override fun run() { - updateGUI(from) + updateGUI("refreshDialog") task = null } } @@ -140,8 +138,8 @@ class LoopDialog : DaggerDialogFragment() { if (_binding == null) return aapsLogger.debug("UpdateGUI from $from") val pumpDescription: PumpDescription = activePlugin.activePump.pumpDescription - val closedLoopAllowed = objectivesPlugin.isClosedLoopAllowed(Constraint(true)) - val lgsEnabled = objectivesPlugin.isLgsAllowed(Constraint(true)) + val closedLoopAllowed = constraintChecker.isClosedLoopAllowed(Constraint(true)) + val lgsEnabled = constraintChecker.isLgsAllowed(Constraint(true)) val apsMode = sp.getString(R.string.key_aps_mode, "open") if (profileFunction.isProfileValid("LoopDialogUpdateGUI")) { if (loopPlugin.isEnabled(PluginType.LOOP)) { diff --git a/app/src/main/java/info/nightscout/androidaps/historyBrowser/HistoryBrowseActivity.kt b/app/src/main/java/info/nightscout/androidaps/historyBrowser/HistoryBrowseActivity.kt index ca4ecbc5d1..c99616c7fc 100644 --- a/app/src/main/java/info/nightscout/androidaps/historyBrowser/HistoryBrowseActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/historyBrowser/HistoryBrowseActivity.kt @@ -270,7 +270,7 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() { val end = start + T.hours(rangeToDisplay.toLong()).msecs() iobCobCalculatorPluginHistory.stopCalculation(from) iobCobCalculatorPluginHistory.clearCache() - iobCobCalculatorPluginHistory.runCalculation(from, end, true, false, eventCustomCalculationFinished) + iobCobCalculatorPluginHistory.runCalculation(from, end, bgDataReload = true, limitDataToOldestAvailable = false, cause = eventCustomCalculationFinished) } } @@ -345,8 +345,8 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() { menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] -> useDSForScale = true } - var alignIobScale = menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] - var alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] + val alignIobScale = menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] + val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] if (menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal]) secondGraphData.addAbsIob(fromTime, toTime, useABSForScale, 1.0) if (menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]) secondGraphData.addIob(fromTime, toTime, useIobForScale, 1.0, menuChartSettings[g + 1][OverviewMenus.CharType.PRE.ordinal], alignIobScale) @@ -365,7 +365,7 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() { } // set manual x bounds to have nice steps - graphData.setNumVerticalLables() + graphData.setNumVerticalLabels() graphData.formatAxis(fromTime, toTime) } // finally enforce drawing of graphs in UI thread 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 index ee0be787e4..a2334030ea 100644 --- 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 @@ -61,6 +61,7 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader var scriptDebug = "" private set + @Suppress("SpellCheckingInspection") operator fun invoke(): DetermineBasalResultAMA? { aapsLogger.debug(LTag.APS, ">>> Invoking determine_basal <<<") aapsLogger.debug(LTag.APS, "Glucose status: " + glucoseStatus.toString().also { glucoseStatusParam = it }) @@ -142,6 +143,7 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader return determineBasalResultAMA } + @Suppress("SpellCheckingInspection") @Throws(JSONException::class) fun setData(profile: Profile, maxIob: Double, maxBasal: Double, @@ -149,7 +151,7 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader maxBg: Double, targetBg: Double, basalRate: Double, - iobArray: Array?, + iobArray: Array, glucoseStatus: GlucoseStatus, mealData: MealData, autosensDataRatio: Double, @@ -196,12 +198,12 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader 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) + this.glucoseStatus.put("delta", glucoseStatus.shortAvgDelta) } else { this.glucoseStatus.put("delta", glucoseStatus.delta) } - this.glucoseStatus.put("short_avgdelta", glucoseStatus.short_avgdelta) - this.glucoseStatus.put("long_avgdelta", glucoseStatus.long_avgdelta) + this.glucoseStatus.put("short_avgdelta", glucoseStatus.shortAvgDelta) + this.glucoseStatus.put("long_avgdelta", glucoseStatus.longAvgDelta) this.mealData = JSONObject() this.mealData.put("carbs", mealData.carbs) this.mealData.put("boluses", mealData.boluses) 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 index 13a11fa774..a980bb1bd4 100644 --- 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 @@ -241,12 +241,12 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader: 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) + mGlucoseStatus.put("delta", glucoseStatus.shortAvgDelta) } else { mGlucoseStatus.put("delta", glucoseStatus.delta) } - mGlucoseStatus.put("short_avgdelta", glucoseStatus.short_avgdelta) - mGlucoseStatus.put("long_avgdelta", glucoseStatus.long_avgdelta) + mGlucoseStatus.put("short_avgdelta", glucoseStatus.shortAvgDelta) + mGlucoseStatus.put("long_avgdelta", glucoseStatus.longAvgDelta) mGlucoseStatus.put("date", glucoseStatus.date) this.mealData.put("carbs", mealData.carbs) this.mealData.put("boluses", mealData.boluses) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPlugin.kt index c5b836ac71..561760abe7 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPlugin.kt @@ -165,7 +165,7 @@ class ObjectivesPlugin @Inject constructor( return value } - fun isLgsAllowed(value: Constraint): Constraint { + override fun isLgsAllowed(value: Constraint): Constraint { if (!objectives[MAXBASAL_OBJECTIVE].isStarted) value.set(aapsLogger, false, String.format(resourceHelper.gs(R.string.objectivenotstarted), MAXBASAL_OBJECTIVE + 1), this) return value diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerDelta.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerDelta.kt index 1fa82bd68f..ce553cb1a3 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerDelta.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerDelta.kt @@ -73,8 +73,8 @@ class TriggerDelta(injector: HasAndroidInjector) : Trigger(injector) { false } val calculatedDelta = when (delta.deltaType) { - DeltaType.SHORT_AVERAGE -> glucoseStatus.short_avgdelta - DeltaType.LONG_AVERAGE -> glucoseStatus.long_avgdelta + DeltaType.SHORT_AVERAGE -> glucoseStatus.shortAvgDelta + DeltaType.LONG_AVERAGE -> glucoseStatus.longAvgDelta else -> glucoseStatus.delta } if (comparator.value.check(calculatedDelta, Profile.toMgdl(delta.value, units))) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/dataBroadcaster/DataBroadcastPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/dataBroadcaster/DataBroadcastPlugin.kt index c6b15b432f..85b557b925 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/dataBroadcaster/DataBroadcastPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/dataBroadcaster/DataBroadcastPlugin.kt @@ -130,7 +130,7 @@ class DataBroadcastPlugin @Inject constructor( bundle.putString("units", profileFunction.getUnits()) // units used in AAPS "mg/dl" or "mmol" bundle.putString("slopeArrow", lastBG.trendArrow.text) // direction arrow as string bundle.putDouble("deltaMgdl", glucoseStatus.delta) // bg delta in mgdl - bundle.putDouble("avgDeltaMgdl", glucoseStatus.avgdelta) // average bg delta + bundle.putDouble("avgDeltaMgdl", glucoseStatus.avgDelta) // average bg delta bundle.putDouble("high", defaultValueHelper.determineHighLine()) // predefined top value of in range (green area) bundle.putDouble("low", defaultValueHelper.determineLowLine()) // predefined bottom value of in range } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/LoggerUtils.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/LoggerUtils.java deleted file mode 100644 index 2d1dcb19b8..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/LoggerUtils.java +++ /dev/null @@ -1,25 +0,0 @@ -package info.nightscout.androidaps.plugins.general.maintenance; - -import org.slf4j.LoggerFactory; - -import ch.qos.logback.classic.LoggerContext; - -/** - * This class provides serveral methods for log-handling (eg. sending logs as emails). - */ -public class LoggerUtils { - - public static String SUFFIX = ".log.zip"; - - /** - * Returns the directory, in which the logs are stored on the system. This is configured in the - * logback.xml file. - * - * @return - */ - public static String getLogDirectory() { - LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); - return lc.getProperty("EXT_FILES_DIR"); - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/LoggerUtils.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/LoggerUtils.kt new file mode 100644 index 0000000000..e6ce7fb821 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/LoggerUtils.kt @@ -0,0 +1,27 @@ +package info.nightscout.androidaps.plugins.general.maintenance + +import ch.qos.logback.classic.LoggerContext +import org.slf4j.LoggerFactory +import javax.inject.Inject +import javax.inject.Singleton + +/** + * This class provides several methods for log-handling (eg. sending logs as emails). + */ +@Singleton +class LoggerUtils @Inject constructor() { + + var suffix = ".log.zip" + + /** + * Returns the directory, in which the logs are stored on the system. This is configured in the + * logback.xml file. + * + * @return + */ + val logDirectory: String + get() { + val lc = LoggerFactory.getILoggerFactory() as LoggerContext + return lc.getProperty("EXT_FILES_DIR") + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt index 6f7cd7401e..e50b19b0ec 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt @@ -34,7 +34,8 @@ class MaintenancePlugin @Inject constructor( private val nsSettingsStatus: NSSettingsStatus, aapsLogger: AAPSLogger, private val buildHelper: BuildHelper, - private val config: Config + private val config: Config, + private val loggerUtils: LoggerUtils ) : PluginBase(PluginDescription() .mainType(PluginType.GENERAL) .fragmentClass(MaintenanceFragment::class.java.name) @@ -51,8 +52,7 @@ class MaintenancePlugin @Inject constructor( fun sendLogs() { val recipient = sp.getString(R.string.key_maintenance_logs_email, "logs@androidaps.org") val amount = sp.getInt(R.string.key_maintenance_logs_amount, 2) - val logDirectory = LoggerUtils.getLogDirectory() - val logs = getLogFiles(logDirectory, amount) + val logs = getLogFiles(amount) val zipDir = context.getExternalFilesDir("exports") val zipFile = File(zipDir, constructName()) aapsLogger.debug("zipFile: ${zipFile.absolutePath}") @@ -66,29 +66,27 @@ class MaintenancePlugin @Inject constructor( //todo replace this with a call on startup of the application, specifically to remove // unnecessary garbage from the log exports fun deleteLogs() { - LoggerUtils.getLogDirectory()?.let { logDirectory -> - val logDir = File(logDirectory) - val files = logDir.listFiles { _: File?, name: String -> - (name.startsWith("AndroidAPS") && name.endsWith(".zip")) + val logDir = File(loggerUtils.logDirectory) + val files = logDir.listFiles { _: File?, name: String -> + (name.startsWith("AndroidAPS") && name.endsWith(".zip")) + } + Arrays.sort(files) { f1: File, f2: File -> f1.name.compareTo(f2.name) } + var delFiles = listOf(*files) + val amount = sp.getInt(R.string.key_logshipper_amount, 2) + val keepIndex = amount - 1 + if (keepIndex < delFiles.size) { + delFiles = delFiles.subList(keepIndex, delFiles.size) + for (file in delFiles) { + file.delete() } - Arrays.sort(files) { f1: File, f2: File -> f1.name.compareTo(f2.name) } - var delFiles = listOf(*files) - val amount = sp.getInt(R.string.key_logshipper_amount, 2) - val keepIndex = amount - 1 - if (keepIndex < delFiles.size) { - delFiles = delFiles.subList(keepIndex, delFiles.size) - for (file in delFiles) { - file.delete() - } - } - val exportDir = File(logDirectory, "exports") - if (exportDir.exists()) { - val expFiles = exportDir.listFiles() - for (file in expFiles) { - file.delete() - } - exportDir.delete() + } + val exportDir = File(loggerUtils.logDirectory, "exports") + if (exportDir.exists()) { + val expFiles = exportDir.listFiles() + for (file in expFiles) { + file.delete() } + exportDir.delete() } } @@ -98,17 +96,16 @@ class MaintenancePlugin @Inject constructor( * * The log files are sorted by the name descending. * - * @param directory * @param amount * @return */ - fun getLogFiles(directory: String, amount: Int): List { - aapsLogger.debug("getting $amount logs from directory $directory") - val logDir = File(directory) + fun getLogFiles(amount: Int): List { + aapsLogger.debug("getting $amount logs from directory ${loggerUtils.logDirectory}") + val logDir = File(loggerUtils.logDirectory) val files = logDir.listFiles { _: File?, name: String -> (name.startsWith("AndroidAPS") && (name.endsWith(".log") - || name.endsWith(".zip") && !name.endsWith(LoggerUtils.SUFFIX))) + || name.endsWith(".zip") && !name.endsWith(loggerUtils.suffix))) } Arrays.sort(files) { f1: File, f2: File -> f2.name.compareTo(f1.name) } val result = listOf(*files) @@ -139,7 +136,7 @@ class MaintenancePlugin @Inject constructor( * @return */ private fun constructName(): String { - return "AndroidAPS_LOG_" + Date().time + LoggerUtils.SUFFIX + return "AndroidAPS_LOG_" + Date().time + loggerUtils.suffix } private fun zip(zipFile: File?, files: List) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt index 79b9e2ec3a..0a427d0da0 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt @@ -588,8 +588,8 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList binding.infoLayout.deltaLarge.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units) binding.infoLayout.deltaLarge.setTextColor(color) binding.infoLayout.delta.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units) - binding.infoLayout.avgDelta.text = Profile.toSignedUnitsString(glucoseStatus.short_avgdelta, glucoseStatus.short_avgdelta * Constants.MGDL_TO_MMOLL, units) - binding.infoLayout.longAvgDelta.text = Profile.toSignedUnitsString(glucoseStatus.long_avgdelta, glucoseStatus.long_avgdelta * Constants.MGDL_TO_MMOLL, units) + binding.infoLayout.avgDelta.text = Profile.toSignedUnitsString(glucoseStatus.shortAvgDelta, glucoseStatus.shortAvgDelta * Constants.MGDL_TO_MMOLL, units) + binding.infoLayout.longAvgDelta.text = Profile.toSignedUnitsString(glucoseStatus.longAvgDelta, glucoseStatus.longAvgDelta * Constants.MGDL_TO_MMOLL, units) } else { binding.infoLayout.delta.text = "Δ " + resourceHelper.gs(R.string.notavailable) binding.infoLayout.avgDelta.text = "" @@ -861,7 +861,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList graphData.addTreatments(fromTime, endTime) // set manual x bounds to have nice steps - graphData.setNumVerticalLables() + graphData.setNumVerticalLabels() graphData.formatAxis(fromTime, endTime) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt index ae0d64e5b0..b0f526b2c7 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt @@ -88,7 +88,7 @@ class GraphData( addSeries(PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] })) } - internal fun setNumVerticalLables() { + internal fun setNumVerticalLabels() { graph.gridLabelRenderer.numVerticalLabels = if (units == Constants.MGDL) (maxY / 40 + 1).toInt() else (maxY / 2 + 1).toInt() } @@ -291,7 +291,7 @@ class GraphData( fun addActivity(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { val actArrayHist: MutableList = ArrayList() - val actArrayPred: MutableList = ArrayList() + val actArrayPrediction: MutableList = ArrayList() val now = System.currentTimeMillis().toDouble() val actScale = Scale() var total: IobTotal @@ -305,7 +305,7 @@ class GraphData( } total = iobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time, profile) val act: Double = total.activity - if (time <= now) actArrayHist.add(ScaledDataPoint(time, act, actScale)) else actArrayPred.add(ScaledDataPoint(time, act, actScale)) + if (time <= now) actArrayHist.add(ScaledDataPoint(time, act, actScale)) else actArrayPrediction.add(ScaledDataPoint(time, act, actScale)) maxIAValue = max(maxIAValue, abs(act)) time += 5 * 60 * 1000L } @@ -314,7 +314,7 @@ class GraphData( it.color = resourceHelper.gc(R.color.activity) it.thickness = 3 }) - addSeries(FixedLineGraphSeries(Array(actArrayPred.size) { i -> actArrayPred[i] }).also { + addSeries(FixedLineGraphSeries(Array(actArrayPrediction.size) { i -> actArrayPrediction[i] }).also { it.setCustomPaint(Paint().also { paint -> paint.style = Paint.Style.STROKE paint.strokeWidth = 3f @@ -332,7 +332,7 @@ class GraphData( //Function below show -BGI to be able to compare curves with deviations fun addMinusBGI(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double, devBgiScale: Boolean) { val bgiArrayHist: MutableList = ArrayList() - val bgiArrayPred: MutableList = ArrayList() + val bgiArrayPrediction: MutableList = ArrayList() val now = System.currentTimeMillis().toDouble() val bgiScale = Scale() var total: IobTotal @@ -348,7 +348,7 @@ class GraphData( total = iobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time, profile) val bgi: Double = total.activity * profile.getIsfMgdl(time) * 5.0 - if (time <= now) bgiArrayHist.add(ScaledDataPoint(time, bgi, bgiScale)) else bgiArrayPred.add(ScaledDataPoint(time, bgi, bgiScale)) + if (time <= now) bgiArrayHist.add(ScaledDataPoint(time, bgi, bgiScale)) else bgiArrayPrediction.add(ScaledDataPoint(time, bgi, bgiScale)) maxBGIValue = max(maxBGIValue, max(abs(bgi), deviation)) time += 5 * 60 * 1000L } @@ -357,7 +357,7 @@ class GraphData( it.color = resourceHelper.gc(R.color.bgi) it.thickness = 3 }) - addSeries(FixedLineGraphSeries(Array(bgiArrayPred.size) { i -> bgiArrayPred[i] }).also { + addSeries(FixedLineGraphSeries(Array(bgiArrayPrediction.size) { i -> bgiArrayPrediction[i] }).also { it.setCustomPaint(Paint().also { paint -> paint.style = Paint.Style.STROKE paint.strokeWidth = 3f @@ -386,7 +386,7 @@ class GraphData( var absIob = 0.0 if (profile != null) { iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time, profile).iob - if (absScale) absIob = iobCobCalculatorPlugin.calculateAbsInsulinFromTreatmentsAndTempsSynchronized(time, profile).iob + if (absScale) absIob = iobCobCalculatorPlugin.calculateAbsInsulinFromTreatmentsAndTempsSynchronized(time).iob } if (abs(lastIob - iob) > 0.02) { if (abs(lastIob - iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, iobScale)) @@ -406,22 +406,22 @@ class GraphData( val autosensData = iobCobCalculatorPlugin.getLastAutosensDataSynchronized("GraphData") val lastAutosensResult = autosensData?.autosensResult ?: AutosensResult() val isTempTarget = treatmentsPlugin.getTempTargetFromHistory(System.currentTimeMillis()) != null - val iobPred: MutableList = ArrayList() - val iobPredArray = iobCobCalculatorPlugin.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget) - for (i in iobPredArray) { - iobPred.add(i.setColor(resourceHelper.gc(R.color.iobPredAS))) + val iobPrediction: MutableList = ArrayList() + val iobPredictionArray = iobCobCalculatorPlugin.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget) + for (i in iobPredictionArray) { + iobPrediction.add(i.setColor(resourceHelper.gc(R.color.iobPredAS))) maxIobValueFound = max(maxIobValueFound, abs(i.iob)) } - addSeries(PointsWithLabelGraphSeries(Array(iobPred.size) { i -> iobPred[i] })) - val iobPred2: MutableList = ArrayList() - val iobPredArray2 = iobCobCalculatorPlugin.calculateIobArrayForSMB(AutosensResult(), SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget) - for (i in iobPredArray2) { - iobPred2.add(i.setColor(resourceHelper.gc(R.color.iobPred))) + addSeries(PointsWithLabelGraphSeries(Array(iobPrediction.size) { i -> iobPrediction[i] })) + val iobPrediction2: MutableList = ArrayList() + val iobPredictionArray2 = iobCobCalculatorPlugin.calculateIobArrayForSMB(AutosensResult(), SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget) + for (i in iobPredictionArray2) { + iobPrediction2.add(i.setColor(resourceHelper.gc(R.color.iobPred))) maxIobValueFound = max(maxIobValueFound, abs(i.iob)) } - addSeries(PointsWithLabelGraphSeries(Array(iobPred2.size) { i -> iobPred2[i] })) - aapsLogger.debug(LTag.AUTOSENS, "IOB pred for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredArray)) - aapsLogger.debug(LTag.AUTOSENS, "IOB pred for AS=" + DecimalFormatter.to2Decimal(1.0) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredArray2)) + addSeries(PointsWithLabelGraphSeries(Array(iobPrediction2.size) { i -> iobPrediction2[i] })) + aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredictionArray)) + aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(1.0) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredictionArray2)) } if (useForScale) { maxY = maxIobValueFound @@ -442,7 +442,7 @@ class GraphData( while (time <= toTime) { val profile = profileFunction.getProfile(time) var iob = 0.0 - if (profile != null) iob = iobCobCalculatorPlugin.calculateAbsInsulinFromTreatmentsAndTempsSynchronized(time, profile).iob + if (profile != null) iob = iobCobCalculatorPlugin.calculateAbsInsulinFromTreatmentsAndTempsSynchronized(time).iob if (abs(lastIob - iob) > 0.02) { if (abs(lastIob - iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, iobScale)) iobArray.add(ScaledDataPoint(time, iob, iobScale)) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.kt index 669ae5c890..0d6defc736 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.kt @@ -140,7 +140,7 @@ class PersistentNotificationPlugin @Inject constructor( line1 = line1aa if (glucoseStatus != null) { line1 += (" Δ" + Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units) - + " avgΔ" + Profile.toSignedUnitsString(glucoseStatus.avgdelta, glucoseStatus.avgdelta * Constants.MGDL_TO_MMOLL, units)) + + " avgΔ" + Profile.toSignedUnitsString(glucoseStatus.avgDelta, glucoseStatus.avgDelta * Constants.MGDL_TO_MMOLL, units)) line1aa += " " + lastBG.trendArrow.symbol } else { line1 += " " + diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java index 151edc457f..9f0c38d1a6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java @@ -320,9 +320,9 @@ public class WatchUpdaterService extends WearableListenerService implements Goog dataMap.putString("delta", "--"); dataMap.putString("avgDelta", "--"); } else { - dataMap.putString("slopeArrow", slopeArrow(glucoseStatus.delta)); - dataMap.putString("delta", deltastring(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units)); - dataMap.putString("avgDelta", deltastring(glucoseStatus.avgdelta, glucoseStatus.avgdelta * Constants.MGDL_TO_MMOLL, units)); + dataMap.putString("slopeArrow", slopeArrow(glucoseStatus.getDelta())); + dataMap.putString("delta", deltastring(glucoseStatus.getDelta(), glucoseStatus.getDelta() * Constants.MGDL_TO_MMOLL, units)); + dataMap.putString("avgDelta", deltastring(glucoseStatus.getAvgDelta(), glucoseStatus.getAvgDelta() * Constants.MGDL_TO_MMOLL, units)); } dataMap.putLong("sgvLevel", sgvLevel); dataMap.putDouble("sgvDouble", lastBG.getValue()); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/insulin/ActivityGraph.java b/app/src/main/java/info/nightscout/androidaps/plugins/insulin/ActivityGraph.java deleted file mode 100644 index ee682272a1..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/insulin/ActivityGraph.java +++ /dev/null @@ -1,78 +0,0 @@ -package info.nightscout.androidaps.plugins.insulin; - -import android.content.Context; -import android.graphics.Color; -import android.util.AttributeSet; - -import com.jjoe64.graphview.GraphView; -import com.jjoe64.graphview.series.DataPoint; -import com.jjoe64.graphview.series.LineGraphSeries; - -import java.util.ArrayList; -import java.util.List; - -import info.nightscout.androidaps.data.Iob; -import info.nightscout.androidaps.db.Treatment; -import info.nightscout.androidaps.interfaces.InsulinInterface; - -/** - * Created by mike on 21.04.2017. - */ - -public class ActivityGraph extends GraphView { - Context context; - - public ActivityGraph(Context context) { - super(context); - this.context = context; - } - - public ActivityGraph(Context context, AttributeSet attrs) { - super(context, attrs); - this.context = context; - } - - public void show(InsulinInterface insulin) { - removeAllSeries(); - mSecondScale = null; - double dia = insulin.getDia(); - int hours = (int) Math.floor(dia + 1); - - Treatment t = new Treatment(); - t.date = 0; - t.insulin = 1d; - - LineGraphSeries activitySeries = null; - LineGraphSeries iobSeries = null; - List activityArray = new ArrayList<>(); - List iobArray = new ArrayList<>(); - - for (long time = 0; time <= hours * 60 * 60 * 1000; time += 5 * 60 * 1000L) { - Iob iob = t.iobCalc(time, dia); - activityArray.add(new DataPoint(time / 60.0 / 1000, iob.activityContrib)); - iobArray.add(new DataPoint(time / 60.0 / 1000, iob.iobContrib)); - } - - DataPoint[] activityDataPoints = new DataPoint[activityArray.size()]; - activityDataPoints = activityArray.toArray(activityDataPoints); - addSeries(activitySeries = new LineGraphSeries<>(activityDataPoints)); - activitySeries.setThickness(8); - - getViewport().setXAxisBoundsManual(true); - getViewport().setMinX(0); - getViewport().setMaxX(hours * 60); - getGridLabelRenderer().setNumHorizontalLabels(hours + 1); - getGridLabelRenderer().setHorizontalAxisTitle("[min]"); - getGridLabelRenderer().setVerticalLabelsColor(activitySeries.getColor()); - - DataPoint[] iobDataPoints = new DataPoint[iobArray.size()]; - iobDataPoints = iobArray.toArray(iobDataPoints); - getSecondScale().addSeries(iobSeries = new LineGraphSeries<>(iobDataPoints)); - iobSeries.setDrawBackground(true); - iobSeries.setColor(Color.MAGENTA); - iobSeries.setBackgroundColor(Color.argb(70, 255, 0, 255)); - getSecondScale().setMinY(0); - getSecondScale().setMaxY(1); - getGridLabelRenderer().setVerticalLabelsSecondScaleColor(Color.MAGENTA); - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/insulin/ActivityGraph.kt b/app/src/main/java/info/nightscout/androidaps/plugins/insulin/ActivityGraph.kt new file mode 100644 index 0000000000..a6575c1886 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/insulin/ActivityGraph.kt @@ -0,0 +1,56 @@ +package info.nightscout.androidaps.plugins.insulin + +import android.content.Context +import android.graphics.Color +import android.util.AttributeSet +import com.jjoe64.graphview.GraphView +import com.jjoe64.graphview.series.DataPoint +import com.jjoe64.graphview.series.LineGraphSeries +import info.nightscout.androidaps.db.Treatment +import info.nightscout.androidaps.interfaces.InsulinInterface +import info.nightscout.androidaps.utils.T +import java.util.* +import kotlin.math.floor + +class ActivityGraph : GraphView { + + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + + fun show(insulin: InsulinInterface) { + removeAllSeries() + mSecondScale = null + val hours = floor(insulin.dia + 1).toLong() + val t = Treatment().also { + it.date = 0 + it.insulin = 1.0 + } + val activityArray: MutableList = ArrayList() + val iobArray: MutableList = ArrayList() + var time: Long = 0 + while (time <= T.hours(hours).msecs()) { + val iob = t.iobCalc(time, insulin.dia) + activityArray.add(DataPoint(T.msecs(time).mins().toDouble(), iob.activityContrib)) + iobArray.add(DataPoint(T.msecs(time).mins().toDouble(), iob.iobContrib)) + time += T.mins(5).msecs() + } + addSeries(LineGraphSeries(Array(activityArray.size) { i -> activityArray[i] }).also { + it.thickness = 8 + gridLabelRenderer.verticalLabelsColor = it.color + }) + viewport.isXAxisBoundsManual = true + viewport.setMinX(0.0) + viewport.setMaxX((hours * 60).toDouble()) + gridLabelRenderer.numHorizontalLabels = (hours + 1).toInt() + gridLabelRenderer.horizontalAxisTitle = "[min]" + secondScale.addSeries(LineGraphSeries(Array(iobArray.size) { i -> iobArray[i] }).also { + it.isDrawBackground = true + it.color = Color.MAGENTA + it.backgroundColor = Color.argb(70, 255, 0, 255) + }) + secondScale.minY = 0.0 + secondScale.maxY = 1.0 + gridLabelRenderer.verticalLabelsSecondScaleColor = Color.MAGENTA + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/BasalData.java b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/BasalData.java deleted file mode 100644 index a4fa66fee1..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/BasalData.java +++ /dev/null @@ -1,11 +0,0 @@ -package info.nightscout.androidaps.plugins.iob.iobCobCalculator; - -/** - * Created by mike on 10.06.2017. - */ - -public class BasalData { - public double basal; - public double tempBasalAbsolute; - public boolean isTempBasalRunning; -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/BasalData.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/BasalData.kt new file mode 100644 index 0000000000..ffeb031b95 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/BasalData.kt @@ -0,0 +1,8 @@ +package info.nightscout.androidaps.plugins.iob.iobCobCalculator + +class BasalData { + + var basal = 0.0 + var tempBasalAbsolute = 0.0 + var isTempBasalRunning = false +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/CobInfo.java b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/CobInfo.java deleted file mode 100644 index fb99067008..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/CobInfo.java +++ /dev/null @@ -1,31 +0,0 @@ -package info.nightscout.androidaps.plugins.iob.iobCobCalculator; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import info.nightscout.androidaps.utils.DecimalFormatter; - -public class CobInfo { - /** All COB up to now, including carbs not yet processed by IobCob calculation. */ - @Nullable - public final Double displayCob; - public final double futureCarbs; - - public CobInfo(@Nullable Double displayCob, double futureCarbs) { - this.displayCob = displayCob; - this.futureCarbs = futureCarbs; - } - - @NonNull - public String generateCOBString() { - String cobStringResult = "--g"; - if (displayCob != null) { - cobStringResult = DecimalFormatter.to0Decimal(displayCob); - if (futureCarbs > 0) { - cobStringResult += "(" + DecimalFormatter.to0Decimal(futureCarbs) + ")"; - } - cobStringResult += "g"; - } - return cobStringResult; - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/CobInfo.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/CobInfo.kt new file mode 100644 index 0000000000..2676fde7b6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/CobInfo.kt @@ -0,0 +1,18 @@ +package info.nightscout.androidaps.plugins.iob.iobCobCalculator + +import info.nightscout.androidaps.utils.DecimalFormatter + +/** All COB up to now, including carbs not yet processed by IobCob calculation. */ +class CobInfo(val displayCob: Double?, val futureCarbs: Double) { + + fun generateCOBString(): String { + var cobStringResult = "--g" + if (displayCob != null) { + cobStringResult = DecimalFormatter.to0Decimal(displayCob) + if (futureCarbs > 0) + cobStringResult += "(${DecimalFormatter.to0Decimal(futureCarbs)})" + cobStringResult += "g" + } + return cobStringResult + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatus.java deleted file mode 100644 index 268f09eaa9..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatus.java +++ /dev/null @@ -1,186 +0,0 @@ -package info.nightscout.androidaps.plugins.iob.iobCobCalculator; - -import androidx.annotation.Nullable; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -import dagger.android.HasAndroidInjector; -import info.nightscout.androidaps.database.entities.GlucoseValue; -import info.nightscout.androidaps.logging.AAPSLogger; -import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.DecimalFormatter; -import info.nightscout.androidaps.utils.Round; - -/** - * Created by mike on 04.01.2017. - */ - -public class GlucoseStatus { - @Inject public AAPSLogger aapsLogger; - @Inject public IobCobCalculatorPlugin iobCobCalculatorPlugin; - - private final HasAndroidInjector injector; - - public double glucose = 0d; - public double noise = 0d; - public double delta = 0d; - public double avgdelta = 0d; - public double short_avgdelta = 0d; - public double long_avgdelta = 0d; - public long date = 0L; - - - public String log() { - return "Glucose: " + DecimalFormatter.to0Decimal(glucose) + " mg/dl " + - "Noise: " + DecimalFormatter.to0Decimal(noise) + " " + - "Delta: " + DecimalFormatter.to0Decimal(delta) + " mg/dl" + - "Short avg. delta: " + " " + DecimalFormatter.to2Decimal(short_avgdelta) + " mg/dl " + - "Long avg. delta: " + DecimalFormatter.to2Decimal(long_avgdelta) + " mg/dl"; - } - - public GlucoseStatus(HasAndroidInjector injector) { - injector.androidInjector().inject(this); - this.injector = injector; - } - - public GlucoseStatus round() { - this.glucose = Round.roundTo(this.glucose, 0.1); - this.noise = Round.roundTo(this.noise, 0.01); - this.delta = Round.roundTo(this.delta, 0.01); - this.avgdelta = Round.roundTo(this.avgdelta, 0.01); - this.short_avgdelta = Round.roundTo(this.short_avgdelta, 0.01); - this.long_avgdelta = Round.roundTo(this.long_avgdelta, 0.01); - return this; - } - - - @Nullable - public GlucoseStatus getGlucoseStatusData() { - return getGlucoseStatusData(false); - } - - @Nullable - public GlucoseStatus getGlucoseStatusData(boolean allowOldData) { - // load 45min - //long fromtime = DateUtil.now() - 60 * 1000L * 45; - //List data = MainApp.getDbHelper().getBgreadingsDataFromTime(fromtime, false); - - synchronized (iobCobCalculatorPlugin.getDataLock()) { - - List data = iobCobCalculatorPlugin.getBgReadings(); - - if (data == null) { - aapsLogger.debug(LTag.GLUCOSE, "data=null"); - return null; - } - - int sizeRecords = data.size(); - if (sizeRecords == 0) { - aapsLogger.debug(LTag.GLUCOSE, "sizeRecords==0"); - return null; - } - - if (data.get(0).getTimestamp() < DateUtil.now() - 7 * 60 * 1000L && !allowOldData) { - aapsLogger.debug(LTag.GLUCOSE, "olddata"); - return null; - } - - GlucoseValue now = data.get(0); - long now_date = now.getTimestamp(); - double change; - - if (sizeRecords == 1) { - GlucoseStatus status = new GlucoseStatus(injector); - status.glucose = now.getValue(); - status.noise = 0d; - status.short_avgdelta = 0d; - status.delta = 0d; - status.long_avgdelta = 0d; - status.avgdelta = 0d; // for OpenAPS MA - status.date = now_date; - aapsLogger.debug(LTag.GLUCOSE, "sizeRecords==1"); - return status.round(); - } - - ArrayList now_value_list = new ArrayList<>(); - ArrayList last_deltas = new ArrayList<>(); - ArrayList short_deltas = new ArrayList<>(); - ArrayList long_deltas = new ArrayList<>(); - - // Use the latest sgv value in the now calculations - now_value_list.add(now.getValue()); - - for (int i = 1; i < sizeRecords; i++) { - if (data.get(i).getValue() > 38) { - GlucoseValue then = data.get(i); - long then_date = then.getTimestamp(); - double avgdelta; - long minutesago; - - minutesago = Math.round((now_date - then_date) / (1000d * 60)); - // multiply by 5 to get the same units as delta, i.e. mg/dL/5m - change = now.getValue() - then.getValue(); - avgdelta = change / minutesago * 5; - - aapsLogger.debug(LTag.GLUCOSE, then.toString() + " minutesago=" + minutesago + " avgdelta=" + avgdelta); - - // use the average of all data points in the last 2.5m for all further "now" calculations - if (0 < minutesago && minutesago < 2.5) { - // Keep and average all values within the last 2.5 minutes - now_value_list.add(then.getValue()); - now.setValue(average(now_value_list)); - // short_deltas are calculated from everything ~5-15 minutes ago - } else if (2.5 < minutesago && minutesago < 17.5) { - //console.error(minutesago, avgdelta); - short_deltas.add(avgdelta); - // last_deltas are calculated from everything ~5 minutes ago - if (2.5 < minutesago && minutesago < 7.5) { - last_deltas.add(avgdelta); - } - // long_deltas are calculated from everything ~20-40 minutes ago - } else if (17.5 < minutesago && minutesago < 42.5) { - long_deltas.add(avgdelta); - } else { - // Do not process any more records after >= 42.5 minutes - break; - } - } - } - - GlucoseStatus status = new GlucoseStatus(injector); - status.glucose = now.getValue(); - status.date = now_date; - status.noise = 0d; //for now set to nothing as not all CGMs report noise - - status.short_avgdelta = average(short_deltas); - - if (last_deltas.isEmpty()) { - status.delta = status.short_avgdelta; - } else { - status.delta = average(last_deltas); - } - - status.long_avgdelta = average(long_deltas); - status.avgdelta = status.short_avgdelta; // for OpenAPS MA - - aapsLogger.debug(LTag.GLUCOSE, status.log()); - return status.round(); - } - } - - public static double average(ArrayList array) { - double sum = 0d; - - if (array.size() == 0) - return 0d; - - for (Double value : array) { - sum += value; - } - return sum / array.size(); - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatus.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatus.kt new file mode 100644 index 0000000000..d6a6c1ff7a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatus.kt @@ -0,0 +1,146 @@ +package info.nightscout.androidaps.plugins.iob.iobCobCalculator + +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.DecimalFormatter +import info.nightscout.androidaps.utils.Round +import java.util.* +import javax.inject.Inject +import kotlin.math.roundToLong + +class GlucoseStatus(private val injector: HasAndroidInjector) { + + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var iobCobCalculatorPlugin: IobCobCalculatorPlugin + + var glucose = 0.0 + var noise = 0.0 + var delta = 0.0 + var avgDelta = 0.0 + var shortAvgDelta = 0.0 + var longAvgDelta = 0.0 + var date = 0L + + init { + injector.androidInjector().inject(this) + } + + fun log(): String { + return "Glucose: " + DecimalFormatter.to0Decimal(glucose) + " mg/dl " + + "Noise: " + DecimalFormatter.to0Decimal(noise) + " " + + "Delta: " + DecimalFormatter.to0Decimal(delta) + " mg/dl" + + "Short avg. delta: " + " " + DecimalFormatter.to2Decimal(shortAvgDelta) + " mg/dl " + + "Long avg. delta: " + DecimalFormatter.to2Decimal(longAvgDelta) + " mg/dl" + } + + fun round(): GlucoseStatus { + glucose = Round.roundTo(glucose, 0.1) + noise = Round.roundTo(noise, 0.01) + delta = Round.roundTo(delta, 0.01) + avgDelta = Round.roundTo(avgDelta, 0.01) + shortAvgDelta = Round.roundTo(shortAvgDelta, 0.01) + longAvgDelta = Round.roundTo(longAvgDelta, 0.01) + return this + } + + val glucoseStatusData: GlucoseStatus? + get() = getGlucoseStatusData(false) + + fun getGlucoseStatusData(allowOldData: Boolean): GlucoseStatus? { + synchronized(iobCobCalculatorPlugin.dataLock) { + val data = iobCobCalculatorPlugin.bgReadings + val sizeRecords = data.size + if (sizeRecords == 0) { + aapsLogger.debug(LTag.GLUCOSE, "sizeRecords==0") + return null + } + if (data[0].timestamp < DateUtil.now() - 7 * 60 * 1000L && !allowOldData) { + aapsLogger.debug(LTag.GLUCOSE, "oldData") + return null + } + val now = data[0] + val nowDate = now.timestamp + var change: Double + if (sizeRecords == 1) { + val status = GlucoseStatus(injector) + status.glucose = now.value + status.noise = 0.0 + status.shortAvgDelta = 0.0 + status.delta = 0.0 + status.longAvgDelta = 0.0 + status.avgDelta = 0.0 // for OpenAPS MA + status.date = nowDate + aapsLogger.debug(LTag.GLUCOSE, "sizeRecords==1") + return status.round() + } + val nowValueList = ArrayList() + val lastDeltas = ArrayList() + val shortDeltas = ArrayList() + val longDeltas = ArrayList() + + // Use the latest sgv value in the now calculations + nowValueList.add(now.value) + for (i in 1 until sizeRecords) { + if (data[i].value > 38) { + val then = data[i] + val thenDate = then.timestamp + + val minutesAgo = ((nowDate - thenDate) / (1000.0 * 60)).roundToLong() + // multiply by 5 to get the same units as delta, i.e. mg/dL/5m + change = now.value - then.value + val avgDel = change / minutesAgo * 5 + aapsLogger.debug(LTag.GLUCOSE, "$then minutesAgo=$minutesAgo avgDelta=$avgDel") + + // use the average of all data points in the last 2.5m for all further "now" calculations + if (0 < minutesAgo && minutesAgo < 2.5) { + // Keep and average all values within the last 2.5 minutes + nowValueList.add(then.value) + now.value = average(nowValueList) + // short_deltas are calculated from everything ~5-15 minutes ago + } else if (2.5 < minutesAgo && minutesAgo < 17.5) { + //console.error(minutesAgo, avgDelta); + shortDeltas.add(avgDel) + // last_deltas are calculated from everything ~5 minutes ago + if (2.5 < minutesAgo && minutesAgo < 7.5) { + lastDeltas.add(avgDel) + } + // long_deltas are calculated from everything ~20-40 minutes ago + } else if (17.5 < minutesAgo && minutesAgo < 42.5) { + longDeltas.add(avgDel) + } else { + // Do not process any more records after >= 42.5 minutes + break + } + } + } + val status = GlucoseStatus(injector) + status.glucose = now.value + status.date = nowDate + status.noise = 0.0 //for now set to nothing as not all CGMs report noise + status.shortAvgDelta = average(shortDeltas) + if (lastDeltas.isEmpty()) { + status.delta = status.shortAvgDelta + } else { + status.delta = average(lastDeltas) + } + status.longAvgDelta = average(longDeltas) + status.avgDelta = status.shortAvgDelta // for OpenAPS MA + aapsLogger.debug(LTag.GLUCOSE, status.log()) + return status.round() + } + } + + companion object { + + fun average(array: ArrayList): Double { + var sum = 0.0 + if (array.size == 0) return 0.0 + for (value in array) { + sum += value + } + return sum / array.size + } + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.java deleted file mode 100644 index 59fdd1ccff..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.java +++ /dev/null @@ -1,968 +0,0 @@ -package info.nightscout.androidaps.plugins.iob.iobCobCalculator; - -import android.os.SystemClock; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.collection.LongSparseArray; - -import org.json.JSONArray; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import javax.inject.Inject; -import javax.inject.Singleton; - -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.database.AppRepository; -import info.nightscout.androidaps.database.entities.GlucoseValue; -import info.nightscout.androidaps.db.TemporaryBasal; -import info.nightscout.androidaps.db.Treatment; -import info.nightscout.androidaps.events.Event; -import info.nightscout.androidaps.events.EventAppInitialized; -import info.nightscout.androidaps.events.EventConfigBuilderChange; -import info.nightscout.androidaps.events.EventNewBG; -import info.nightscout.androidaps.events.EventNewBasalProfile; -import info.nightscout.androidaps.events.EventPreferenceChange; -import info.nightscout.androidaps.interfaces.ActivePluginProvider; -import info.nightscout.androidaps.interfaces.IobCobCalculatorInterface; -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.logging.AAPSLogger; -import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.plugins.bus.RxBusWrapper; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryBgData; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData; -import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin; -import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin; -import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin; -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.DecimalFormatter; -import info.nightscout.androidaps.utils.FabricPrivacy; -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 static info.nightscout.androidaps.utils.DateUtil.now; - -@Singleton -public class IobCobCalculatorPlugin extends PluginBase implements IobCobCalculatorInterface { - private final HasAndroidInjector injector; - private final AapsSchedulers aapsSchedulers; - private final SP sp; - private final RxBusWrapper rxBus; - private final ResourceHelper resourceHelper; - private final ProfileFunction profileFunction; - private final ActivePluginProvider activePlugin; - private final TreatmentsPlugin treatmentsPlugin; - private final SensitivityOref1Plugin sensitivityOref1Plugin; - private final SensitivityAAPSPlugin sensitivityAAPSPlugin; - private final SensitivityWeightedAveragePlugin sensitivityWeightedAveragePlugin; - private final FabricPrivacy fabricPrivacy; - private final DateUtil dateUtil; - private final AppRepository repository; - - private final CompositeDisposable disposable = new CompositeDisposable(); - - private LongSparseArray iobTable = new LongSparseArray<>(); // oldest at index 0 - private LongSparseArray absIobTable = new LongSparseArray<>(); // oldest at index 0, absolute insulin in the body - private LongSparseArray autosensDataTable = new LongSparseArray<>(); // oldest at index 0 - private LongSparseArray basalDataTable = new LongSparseArray<>(); // oldest at index 0 - - private volatile List bgReadings = null; // newest at index 0 - private volatile List bucketed_data = null; - - // we need to make sure that bucketed_data will always have the same timestamp for correct use of cached values - // once referenceTime != null all bucketed data should be (x * 5min) from referenceTime - Long referenceTime = null; - private Boolean lastUsed5minCalculation = null; // true if used 5min bucketed data - - private final Object dataLock = new Object(); - - boolean stopCalculationTrigger = false; - private Thread thread = null; - - @Inject - public IobCobCalculatorPlugin( - HasAndroidInjector injector, - AAPSLogger aapsLogger, - AapsSchedulers aapsSchedulers, - RxBusWrapper rxBus, - SP sp, - ResourceHelper resourceHelper, - ProfileFunction profileFunction, - ActivePluginProvider activePlugin, - TreatmentsPlugin treatmentsPlugin, - SensitivityOref1Plugin sensitivityOref1Plugin, - SensitivityAAPSPlugin sensitivityAAPSPlugin, - SensitivityWeightedAveragePlugin sensitivityWeightedAveragePlugin, - FabricPrivacy fabricPrivacy, - DateUtil dateUtil, - AppRepository repository - ) { - super(new PluginDescription() - .mainType(PluginType.GENERAL) - .pluginName(R.string.iobcobcalculator) - .showInList(false) - .neverVisible(true) - .alwaysEnabled(true), - aapsLogger, resourceHelper, injector - ); - this.injector = injector; - this.aapsSchedulers = aapsSchedulers; - this.sp = sp; - this.rxBus = rxBus; - this.resourceHelper = resourceHelper; - this.profileFunction = profileFunction; - this.activePlugin = activePlugin; - this.treatmentsPlugin = treatmentsPlugin; - this.sensitivityOref1Plugin = sensitivityOref1Plugin; - this.sensitivityAAPSPlugin = sensitivityAAPSPlugin; - this.sensitivityWeightedAveragePlugin = sensitivityWeightedAveragePlugin; - this.fabricPrivacy = fabricPrivacy; - this.dateUtil = dateUtil; - this.repository = repository; - } - - @Override - protected void onStart() { - super.onStart(); - // EventConfigBuilderChange - disposable.add(rxBus - .toObservable(EventConfigBuilderChange.class) - .observeOn(aapsSchedulers.getIo()) - .subscribe(event -> { - stopCalculation("onEventConfigBuilderChange"); - synchronized (dataLock) { - getAapsLogger().debug(LTag.AUTOSENS, "Invalidating cached data because of configuration change."); - resetData(); - } - runCalculation("onEventConfigBuilderChange", System.currentTimeMillis(), false, true, event); - }, fabricPrivacy::logException) - ); - // EventNewBasalProfile - disposable.add(rxBus - .toObservable(EventNewBasalProfile.class) - .observeOn(aapsSchedulers.getIo()) - .subscribe(event -> { - if (event == null) { // on init no need of reset - return; - } - stopCalculation("onNewProfile"); - synchronized (dataLock) { - getAapsLogger().debug(LTag.AUTOSENS, "Invalidating cached data because of new profile."); - resetData(); - } - runCalculation("onNewProfile", System.currentTimeMillis(), false, true, event); - }, fabricPrivacy::logException) - ); - // EventNewBG .... cannot be used for invalidating because only event with last BG is fired - disposable.add(rxBus - .toObservable(EventNewBG.class) - .observeOn(aapsSchedulers.getIo()) - .debounce(1L, TimeUnit.SECONDS) - .subscribe(event -> { - stopCalculation("onEventNewBG"); - runCalculation("onEventNewBG", System.currentTimeMillis(), true, true, event); - }, fabricPrivacy::logException) - ); - // EventPreferenceChange - disposable.add(rxBus - .toObservable(EventPreferenceChange.class) - .observeOn(aapsSchedulers.getIo()) - .subscribe(event -> { - if (event.isChanged(resourceHelper, R.string.key_openapsama_autosens_period) || - event.isChanged(resourceHelper, R.string.key_age) || - event.isChanged(resourceHelper, R.string.key_absorption_maxtime) || - event.isChanged(resourceHelper, R.string.key_openapsama_min_5m_carbimpact) || - event.isChanged(resourceHelper, R.string.key_absorption_cutoff) || - event.isChanged(resourceHelper, R.string.key_openapsama_autosens_max) || - event.isChanged(resourceHelper, R.string.key_openapsama_autosens_min) || - event.isChanged(resourceHelper, R.string.key_insulin_oref_peak) - ) { - stopCalculation("onEventPreferenceChange"); - synchronized (dataLock) { - getAapsLogger().debug(LTag.AUTOSENS, "Invalidating cached data because of preference change."); - resetData(); - } - runCalculation("onEventPreferenceChange", System.currentTimeMillis(), false, true, event); - } - }, fabricPrivacy::logException) - ); - // EventAppInitialized - disposable.add(rxBus - .toObservable(EventAppInitialized.class) - .observeOn(aapsSchedulers.getIo()) - .subscribe(event -> runCalculation("onEventAppInitialized", System.currentTimeMillis(), true, true, event), fabricPrivacy::logException) - ); - // EventNewHistoryData - disposable.add(rxBus - .toObservable(EventNewHistoryData.class) - .observeOn(aapsSchedulers.getIo()) - .subscribe(event -> newHistoryData(event, false), fabricPrivacy::logException) - ); - // EventNewHistoryBgData - disposable.add(rxBus - .toObservable(EventNewHistoryBgData.class) - .observeOn(aapsSchedulers.getIo()) - .subscribe(event -> newHistoryData(new EventNewHistoryData(event.getTimestamp()), true), fabricPrivacy::logException) - ); - } - - @Override - protected void onStop() { - disposable.clear(); - super.onStop(); - } - - public LongSparseArray getAutosensDataTable() { - return autosensDataTable; - } - - public List getBgReadings() { - return bgReadings; - } - - public void setBgReadings(List bgReadings) { - this.bgReadings = bgReadings; - } - - public List getBucketedData() { - return bucketed_data; - } - - public Object getDataLock() { - return dataLock; - } - - // roundup to whole minute - public static long roundUpTime(long time) { - if (time % 60000 == 0) - return time; - long rounded = (time / 60000 + 1) * 60000; - return rounded; - } - - long adjustToReferenceTime(long someTime) { - if (referenceTime == null) { - referenceTime = someTime; - return someTime; - } - long diff = Math.abs(someTime - referenceTime); - diff %= T.mins(5).msecs(); - if (diff > T.mins(2).plus(T.secs(30)).msecs()) - diff = diff - T.mins(5).msecs(); - long newTime = someTime + diff; - return newTime; - } - - void loadBgData(long to) { - Profile profile = profileFunction.getProfile(to); - double dia = Constants.defaultDIA; - if (profile != null) dia = profile.getDia(); - long start = to - T.hours((long) (24 + dia)).msecs(); - if (DateUtil.isCloseToNow(to)) { - // if close to now expect there can be some readings with time in close future (caused by wrong time setting) - // so read all records - bgReadings = repository.compatGetBgReadingsDataFromTime(start, false).blockingGet(); - getAapsLogger().debug(LTag.AUTOSENS, "BG data loaded. Size: " + bgReadings.size() + " Start date: " + dateUtil.dateAndTimeString(start)); - } else { - bgReadings = repository.compatGetBgReadingsDataFromTime(start, to, false).blockingGet(); - getAapsLogger().debug(LTag.AUTOSENS, "BG data loaded. Size: " + bgReadings.size() + " Start date: " + dateUtil.dateAndTimeString(start) + " End date: " + dateUtil.dateAndTimeString(to)); - } - } - - public boolean isAbout5minData() { - synchronized (dataLock) { - if (bgReadings == null || bgReadings.size() < 3) { - return true; - } - long totalDiff = 0; - for (int i = 1; i < bgReadings.size(); ++i) { - long bgTime = bgReadings.get(i).getTimestamp(); - long lastbgTime = bgReadings.get(i - 1).getTimestamp(); - long diff = lastbgTime - bgTime; - diff %= T.mins(5).msecs(); - if (diff > T.mins(2).plus(T.secs(30)).msecs()) - diff = diff - T.mins(5).msecs(); - totalDiff += diff; - diff = Math.abs(diff); - if (diff > T.secs(30).msecs()) { - getAapsLogger().debug(LTag.AUTOSENS, "Interval detection: values: " + bgReadings.size() + " diff: " + (diff / 1000) + "[s] is5minData: " + false); - return false; - } - } - long averageDiff = totalDiff / bgReadings.size() / 1000; - boolean is5mindata = averageDiff < 1; - getAapsLogger().debug(LTag.AUTOSENS, "Interval detection: values: " + bgReadings.size() + " averageDiff: " + averageDiff + "[s] is5minData: " + is5mindata); - return is5mindata; - } - } - - private void resetData() { - synchronized (dataLock) { - iobTable = new LongSparseArray<>(); - autosensDataTable = new LongSparseArray<>(); - basalDataTable = new LongSparseArray<>(); - absIobTable = new LongSparseArray<>(); - } - } - - public void createBucketedData() { - boolean fiveMinData = isAbout5minData(); - if (lastUsed5minCalculation != null && lastUsed5minCalculation != fiveMinData) { - // changing mode => clear cache - getAapsLogger().debug("Invalidating cached data because of changed mode."); - resetData(); - } - lastUsed5minCalculation = fiveMinData; - if (isAbout5minData()) - createBucketedData5min(); - else - createBucketedDataRecalculated(); - } - - @Nullable - public GlucoseValue findNewer(long time) { - GlucoseValue lastFound = bgReadings.get(0); - if (lastFound.getTimestamp() < time) return null; - for (int i = 1; i < bgReadings.size(); ++i) { - if (bgReadings.get(i).getTimestamp() == time) return bgReadings.get(i); - if (bgReadings.get(i).getTimestamp() > time) continue; - lastFound = bgReadings.get(i - 1); - if (bgReadings.get(i).getTimestamp() < time) break; - } - return lastFound; - } - - @Nullable - public GlucoseValue findOlder(long time) { - GlucoseValue lastFound = bgReadings.get(bgReadings.size() - 1); - if (lastFound.getTimestamp() > time) return null; - for (int i = bgReadings.size() - 2; i >= 0; --i) { - if (bgReadings.get(i).getTimestamp() == time) return bgReadings.get(i); - if (bgReadings.get(i).getTimestamp() < time) continue; - lastFound = bgReadings.get(i + 1); - if (bgReadings.get(i).getTimestamp() > time) break; - } - return lastFound; - } - - private void createBucketedDataRecalculated() { - if (bgReadings == null || bgReadings.size() < 3) { - bucketed_data = null; - return; - } - - bucketed_data = new ArrayList<>(); - long currentTime = bgReadings.get(0).getTimestamp() - bgReadings.get(0).getTimestamp() % T.mins(5).msecs(); - currentTime = adjustToReferenceTime(currentTime); - getAapsLogger().debug("Adjusted time " + dateUtil.dateAndTimeAndSecondsString(currentTime)); - //log.debug("First reading: " + new Date(currentTime).toLocaleString()); - - while (true) { - // test if current value is older than current time - GlucoseValue newer = findNewer(currentTime); - GlucoseValue older = findOlder(currentTime); - if (newer == null || older == null) - break; - - if (older.getTimestamp() == newer.getTimestamp()) { // direct hit - bucketed_data.add(new InMemoryGlucoseValue(newer)); - } else { - double bgDelta = newer.getValue() - older.getValue(); - long timeDiffToNew = newer.getTimestamp() - currentTime; - - double currentBg = newer.getValue() - (double) timeDiffToNew / (newer.getTimestamp() - older.getTimestamp()) * bgDelta; - InMemoryGlucoseValue newBgreading = new InMemoryGlucoseValue(currentTime, Math.round(currentBg), true); - bucketed_data.add(newBgreading); - //log.debug("BG: " + newBgreading.value + " (" + new Date(newBgreading.date).toLocaleString() + ") Prev: " + older.value + " (" + new Date(older.date).toLocaleString() + ") Newer: " + newer.value + " (" + new Date(newer.date).toLocaleString() + ")"); - } - currentTime -= T.mins(5).msecs(); - - } - } - - - private void createBucketedData5min() { - if (bgReadings == null || bgReadings.size() < 3) { - bucketed_data = null; - return; - } - - bucketed_data = new ArrayList<>(); - bucketed_data.add(new InMemoryGlucoseValue(bgReadings.get(0))); - getAapsLogger().debug(LTag.AUTOSENS, "Adding. bgTime: " + DateUtil.toISOString(bgReadings.get(0).getTimestamp()) + " lastbgTime: " + "none-first-value" + " " + bgReadings.get(0).toString()); - int j = 0; - for (int i = 1; i < bgReadings.size(); ++i) { - long bgTime = bgReadings.get(i).getTimestamp(); - long lastbgTime = bgReadings.get(i - 1).getTimestamp(); - //log.error("Processing " + i + ": " + new Date(bgTime).toString() + " " + bgReadings.get(i).value + " Previous: " + new Date(lastbgTime).toString() + " " + bgReadings.get(i - 1).value); - if (bgReadings.get(i).getValue() < 39 || bgReadings.get(i - 1).getValue() < 39) { - throw new IllegalStateException("<39"); - } - - long elapsed_minutes = (bgTime - lastbgTime) / (60 * 1000); - if (Math.abs(elapsed_minutes) > 8) { - // interpolate missing data points - double lastbg = bgReadings.get(i - 1).getValue(); - elapsed_minutes = Math.abs(elapsed_minutes); - //console.error(elapsed_minutes); - long nextbgTime; - while (elapsed_minutes > 5) { - nextbgTime = lastbgTime - 5 * 60 * 1000; - j++; - double gapDelta = bgReadings.get(i).getValue() - lastbg; - //console.error(gapDelta, lastbg, elapsed_minutes); - double nextbg = lastbg + (5d / elapsed_minutes * gapDelta); - InMemoryGlucoseValue newBgreading = new InMemoryGlucoseValue(nextbgTime, Math.round(nextbg), true); - //console.error("Interpolated", bucketed_data[j]); - bucketed_data.add(newBgreading); - getAapsLogger().debug(LTag.AUTOSENS, "Adding. bgTime: " + DateUtil.toISOString(bgTime) + " lastbgTime: " + DateUtil.toISOString(lastbgTime) + " " + newBgreading.toString()); - - elapsed_minutes = elapsed_minutes - 5; - lastbg = nextbg; - lastbgTime = nextbgTime; - } - j++; - InMemoryGlucoseValue newBgreading = new InMemoryGlucoseValue(bgTime, bgReadings.get(i).getValue()); - bucketed_data.add(newBgreading); - getAapsLogger().debug(LTag.AUTOSENS, "Adding. bgTime: " + DateUtil.toISOString(bgTime) + " lastbgTime: " + DateUtil.toISOString(lastbgTime) + " " + newBgreading.toString()); - } else if (Math.abs(elapsed_minutes) > 2) { - j++; - InMemoryGlucoseValue newBgreading = new InMemoryGlucoseValue(bgTime, bgReadings.get(i).getValue()); - bucketed_data.add(newBgreading); - getAapsLogger().debug(LTag.AUTOSENS, "Adding. bgTime: " + DateUtil.toISOString(bgTime) + " lastbgTime: " + DateUtil.toISOString(lastbgTime) + " " + newBgreading.toString()); - } else { - bucketed_data.get(j).setValue((bucketed_data.get(j).getValue() + bgReadings.get(i).getValue()) / 2); - //log.error("***** Average"); - } - } - - // Normalize bucketed data - InMemoryGlucoseValue oldest = bucketed_data.get(bucketed_data.size() - 1); - oldest.setTimestamp(adjustToReferenceTime(oldest.getTimestamp())); - getAapsLogger().debug("Adjusted time " + dateUtil.dateAndTimeAndSecondsString(oldest.getTimestamp())); - for (int i = bucketed_data.size() - 2; i >= 0; i--) { - InMemoryGlucoseValue current = bucketed_data.get(i); - InMemoryGlucoseValue previous = bucketed_data.get(i + 1); - long msecDiff = current.getTimestamp() - previous.getTimestamp(); - long adjusted = (msecDiff - T.mins(5).msecs()) / 1000; - getAapsLogger().debug(LTag.AUTOSENS, "Adjusting bucketed data time. Current: " + dateUtil.dateAndTimeAndSecondsString(current.getTimestamp()) + " to: " + dateUtil.dateAndTimeAndSecondsString(previous.getTimestamp() + T.mins(5).msecs()) + " by " + adjusted + " sec"); - if (Math.abs(adjusted) > 90) { - // too big adjustment, fallback to non 5 min data - getAapsLogger().debug(LTag.AUTOSENS, "Fallback to non 5 min data"); - createBucketedDataRecalculated(); - return; - } - current.setTimestamp(previous.getTimestamp() + T.mins(5).msecs()); - } - - getAapsLogger().debug(LTag.AUTOSENS, "Bucketed data created. Size: " + bucketed_data.size()); - } - - long calculateDetectionStart(long from, boolean limitDataToOldestAvailable) { - Profile profile = profileFunction.getProfile(from); - double dia = Constants.defaultDIA; - if (profile != null) dia = profile.getDia(); - - long oldestDataAvailable = treatmentsPlugin.oldestDataAvailable(); - long getBGDataFrom; - if (limitDataToOldestAvailable) { - getBGDataFrom = Math.max(oldestDataAvailable, (long) (from - T.hours(1).msecs() * (24 + dia))); - if (getBGDataFrom == oldestDataAvailable) - getAapsLogger().debug(LTag.AUTOSENS, "Limiting data to oldest available temps: " + dateUtil.dateAndTimeAndSecondsString(oldestDataAvailable)); - } else - getBGDataFrom = (long) (from - T.hours(1).msecs() * (24 + dia)); - return getBGDataFrom; - } - - public IobTotal calculateFromTreatmentsAndTempsSynchronized(long time, Profile profile) { - synchronized (dataLock) { - return calculateFromTreatmentsAndTemps(time, profile); - } - } - - private IobTotal calculateFromTreatmentsAndTempsSynchronized(long time, AutosensResult lastAutosensResult, boolean exercise_mode, int half_basal_exercise_target, boolean isTempTarget) { - synchronized (dataLock) { - return calculateFromTreatmentsAndTemps(time, lastAutosensResult, exercise_mode, half_basal_exercise_target, isTempTarget); - } - } - - IobTotal calculateFromTreatmentsAndTemps(long time, Profile profile) { - long now = System.currentTimeMillis(); - time = roundUpTime(time); - if (time < now && iobTable.get(time) != null) { - //og.debug(">>> calculateFromTreatmentsAndTemps Cache hit " + new Date(time).toLocaleString()); - return iobTable.get(time); - } else { - //log.debug(">>> calculateFromTreatmentsAndTemps Cache miss " + new Date(time).toLocaleString()); - } - IobTotal bolusIob = treatmentsPlugin.getCalculationToTimeTreatments(time).round(); - IobTotal basalIob = treatmentsPlugin.getCalculationToTimeTempBasals(time, true, now).round(); - // OpenAPSSMB only - // Add expected zero temp basal for next 240 mins - IobTotal basalIobWithZeroTemp = basalIob.copy(); - TemporaryBasal t = new TemporaryBasal(injector) - .date(now + 60 * 1000L) - .duration(240) - .absolute(0); - if (t.date < time) { - IobTotal calc = t.iobCalc(time, profile); - basalIobWithZeroTemp.plus(calc); - } - - basalIob.iobWithZeroTemp = IobTotal.combine(bolusIob, basalIobWithZeroTemp).round(); - - IobTotal iobTotal = IobTotal.combine(bolusIob, basalIob).round(); - if (time < System.currentTimeMillis()) { - iobTable.put(time, iobTotal); - } - return iobTotal; - } - - public IobTotal calculateAbsInsulinFromTreatmentsAndTempsSynchronized(long time, Profile profile) { - synchronized (dataLock) { - long now = System.currentTimeMillis(); - time = roundUpTime(time); - if (time < now && absIobTable.get(time) != null) { - //og.debug(">>> calculateFromTreatmentsAndTemps Cache hit " + new Date(time).toLocaleString()); - return absIobTable.get(time); - } else { - //log.debug(">>> calculateFromTreatmentsAndTemps Cache miss " + new Date(time).toLocaleString()); - } - IobTotal bolusIob = treatmentsPlugin.getCalculationToTimeTreatments(time).round(); - IobTotal basalIob = treatmentsPlugin.getAbsoluteIOBTempBasals(time).round(); - - IobTotal iobTotal = IobTotal.combine(bolusIob, basalIob).round(); - if (time < System.currentTimeMillis()) { - absIobTable.put(time, iobTotal); - } - return iobTotal; - } - } - - private IobTotal calculateFromTreatmentsAndTemps(long time, AutosensResult lastAutosensResult, boolean exercise_mode, int half_basal_exercise_target, boolean isTempTarget) { - long now = DateUtil.now(); - - IobTotal bolusIob = treatmentsPlugin.getCalculationToTimeTreatments(time).round(); - IobTotal basalIob = treatmentsPlugin.getCalculationToTimeTempBasals(time, now, lastAutosensResult, exercise_mode, half_basal_exercise_target, isTempTarget).round(); - // OpenAPSSMB only - // Add expected zero temp basal for next 240 mins - IobTotal basalIobWithZeroTemp = basalIob.copy(); - TemporaryBasal t = new TemporaryBasal(injector) - .date(now + 60 * 1000L) - .duration(240) - .absolute(0); - if (t.date < time) { - Profile profile = profileFunction.getProfile(t.date); - if (profile != null) { - IobTotal calc = t.iobCalc(time, profile, lastAutosensResult, exercise_mode, half_basal_exercise_target, isTempTarget); - basalIobWithZeroTemp.plus(calc); - } - } - - basalIob.iobWithZeroTemp = IobTotal.combine(bolusIob, basalIobWithZeroTemp).round(); - - return IobTotal.combine(bolusIob, basalIob).round(); - } - - @Nullable - public Long findPreviousTimeFromBucketedData(long time) { - if (bucketed_data == null) - return null; - for (int index = 0; index < bucketed_data.size(); index++) { - if (bucketed_data.get(index).getTimestamp() <= time) - return bucketed_data.get(index).getTimestamp(); - } - return null; - } - - public BasalData getBasalData(Profile profile, long time) { - synchronized (dataLock) { - long now = System.currentTimeMillis(); - time = roundUpTime(time); - BasalData retval = basalDataTable.get(time); - if (retval == null) { - retval = new BasalData(); - TemporaryBasal tb = treatmentsPlugin.getTempBasalFromHistory(time); - retval.basal = profile.getBasal(time); - if (tb != null) { - retval.isTempBasalRunning = true; - retval.tempBasalAbsolute = tb.tempBasalConvertedToAbsolute(time, profile); - } else { - retval.isTempBasalRunning = false; - retval.tempBasalAbsolute = retval.basal; - } - if (time < now) { - basalDataTable.append(time, retval); - } - //log.debug(">>> getBasalData Cache miss " + new Date(time).toLocaleString()); - } else { - //log.debug(">>> getBasalData Cache hit " + new Date(time).toLocaleString()); - } - return retval; - } - } - - @Nullable - public AutosensData getAutosensData(long time) { - synchronized (dataLock) { - long now = System.currentTimeMillis(); - if (time > now) { - return null; - } - Long previous = findPreviousTimeFromBucketedData(time); - if (previous == null) { - return null; - } - time = roundUpTime(previous); - AutosensData data = autosensDataTable.get(time); - if (data != null) { - //log.debug(">>> AUTOSENSDATA Cache hit " + data.toString()); - return data; - } else { - //log.debug(">>> AUTOSENSDATA Cache miss " + new Date(time).toLocaleString()); - return null; - } - } - } - - @Nullable - public AutosensData getLastAutosensDataSynchronized(String reason) { - if (thread != null && thread.isAlive()) { - getAapsLogger().debug(LTag.AUTOSENS, "AUTOSENSDATA is waiting for calculation thread: " + reason); - try { - thread.join(5000); - } catch (InterruptedException ignored) { - } - getAapsLogger().debug(LTag.AUTOSENS, "AUTOSENSDATA finished waiting for calculation thread: " + reason); - } - synchronized (dataLock) { - return getLastAutosensData(reason); - } - } - - - @NonNull - public CobInfo getCobInfo(boolean _synchronized, String reason) { - AutosensData autosensData = _synchronized ? getLastAutosensDataSynchronized(reason) : getLastAutosensData(reason); - Double displayCob = null; - double futureCarbs = 0; - long now = now(); - List treatments = treatmentsPlugin.getTreatmentsFromHistory(); - - if (autosensData != null) { - displayCob = autosensData.cob; - for (Treatment treatment : treatments) { - if (!treatment.isValid) continue; - if (IobCobCalculatorPlugin.roundUpTime(treatment.date) > IobCobCalculatorPlugin.roundUpTime(autosensData.time) - && treatment.date <= now && treatment.carbs > 0) { - displayCob += treatment.carbs; - } - } - } - for (Treatment treatment : treatments) { - if (!treatment.isValid) continue; - if (treatment.date > now && treatment.carbs > 0) { - futureCarbs += treatment.carbs; - } - } - return new CobInfo(displayCob, futureCarbs); - } - - public double slowAbsorptionPercentage(int timeInMinutes) { - double sum = 0; - int count = 0; - int valuesToProcess = timeInMinutes / 5; - synchronized (dataLock) { - for (int i = autosensDataTable.size() - 1; i >= 0 && count < valuesToProcess; i--) { - if (autosensDataTable.valueAt(i).failoverToMinAbsorbtionRate) - sum++; - count++; - } - } - return sum / count; - } - - @Nullable - public AutosensData getLastAutosensData(String reason) { - if (autosensDataTable.size() < 1) { - getAapsLogger().debug(LTag.AUTOSENS, "AUTOSENSDATA null: autosensDataTable empty (" + reason + ")"); - return null; - } - AutosensData data; - try { - data = autosensDataTable.valueAt(autosensDataTable.size() - 1); - } catch (Exception e) { - // data can be processed on the background - // in this rare case better return null and do not block UI - // APS plugin should use getLastAutosensDataSynchronized where the blocking is not an issue - getAapsLogger().error("AUTOSENSDATA null: Exception catched (" + reason + ")"); - return null; - } - if (data == null) { - getAapsLogger().error("AUTOSENSDATA null: data==null"); - return null; - } - if (data.time < System.currentTimeMillis() - 11 * 60 * 1000) { - getAapsLogger().debug(LTag.AUTOSENS, "AUTOSENSDATA null: data is old (" + reason + ") size()=" + autosensDataTable.size() + " lastdata=" + dateUtil.dateAndTimeAndSecondsString(data.time)); - return null; - } else { - getAapsLogger().debug(LTag.AUTOSENS, "AUTOSENSDATA (" + reason + ") " + data.toString()); - return data; - } - } - - @Override - public String lastDataTime() { - if (autosensDataTable.size() > 0) - return dateUtil.dateAndTimeAndSecondsString(autosensDataTable.valueAt(autosensDataTable.size() - 1).time); - else - return "autosensDataTable empty"; - } - - public MealData getMealData() { - MealData result = new MealData(); - - Profile profile = profileFunction.getProfile(); - if (profile == null) return result; - - long now = System.currentTimeMillis(); - long dia_ago = now - (Double.valueOf(profile.getDia() * T.hours(1).msecs())).longValue(); - - double maxAbsorptionHours = Constants.DEFAULT_MAX_ABSORPTION_TIME; - if (sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled()) { - maxAbsorptionHours = sp.getDouble(R.string.key_absorption_maxtime, Constants.DEFAULT_MAX_ABSORPTION_TIME); - } else { - maxAbsorptionHours = sp.getDouble(R.string.key_absorption_cutoff, Constants.DEFAULT_MAX_ABSORPTION_TIME); - } - long absorptionTime_ago = now - (Double.valueOf(maxAbsorptionHours * T.hours(1).msecs())).longValue(); - - List treatments = treatmentsPlugin.getTreatmentsFromHistory(); - for (Treatment treatment : treatments) { - if (!treatment.isValid) - continue; - long t = treatment.date; - - if (t > dia_ago && t <= now) { - if (treatment.insulin > 0 && treatment.mealBolus) { - result.boluses += treatment.insulin; - } - } - if (t > absorptionTime_ago && t <= now) { - if (treatment.carbs >= 1) { - result.carbs += treatment.carbs; - if (t > result.lastCarbTime) - result.lastCarbTime = t; - } - } - } - - AutosensData autosensData = getLastAutosensDataSynchronized("getMealData()"); - if (autosensData != null) { - result.mealCOB = autosensData.cob; - result.slopeFromMinDeviation = autosensData.slopeFromMinDeviation; - result.slopeFromMaxDeviation = autosensData.slopeFromMaxDeviation; - result.usedMinCarbsImpact = autosensData.usedMinCarbsImpact; - } - result.lastBolusTime = treatmentsPlugin.getLastBolusTime(); - return result; - } - - public IobTotal[] calculateIobArrayInDia(Profile profile) { - // predict IOB out to DIA plus 30m - long time = System.currentTimeMillis(); - time = roundUpTime(time); - int len = (int) ((profile.getDia() * 60 + 30) / 5); - IobTotal[] array = new IobTotal[len]; - int pos = 0; - for (int i = 0; i < len; i++) { - long t = time + i * 5 * 60000; - IobTotal iob = calculateFromTreatmentsAndTempsSynchronized(t, profile); - array[pos] = iob; - pos++; - } - return array; - } - - public IobTotal[] calculateIobArrayForSMB(AutosensResult lastAutosensResult, boolean exercise_mode, int half_basal_exercise_target, boolean isTempTarget) { - // predict IOB out to DIA plus 30m - long now = DateUtil.now(); - int len = (4 * 60) / 5; - IobTotal[] array = new IobTotal[len]; - int pos = 0; - for (int i = 0; i < len; i++) { - long t = now + i * 5 * 60000; - IobTotal iob = calculateFromTreatmentsAndTempsSynchronized(t, lastAutosensResult, exercise_mode, half_basal_exercise_target, isTempTarget); - array[pos] = iob; - pos++; - } - return array; - } - - public String iobArrayToString(IobTotal[] array) { - StringBuilder sb = new StringBuilder(); - sb.append("["); - for (IobTotal i : array) { - sb.append(DecimalFormatter.to2Decimal(i.iob)); - sb.append(", "); - } - sb.append("]"); - return sb.toString(); - } - - AutosensResult detectSensitivityWithLock(long fromTime, long toTime) { - synchronized (dataLock) { - return activePlugin.getActiveSensitivity().detectSensitivity(this, fromTime, toTime); - } - } - - public static JSONArray convertToJSONArray(IobTotal[] iobArray) { - JSONArray array = new JSONArray(); - for (int i = 0; i < iobArray.length; i++) { - array.put(iobArray[i].determineBasalJson()); - } - return array; - } - - public void stopCalculation(String from) { - if (thread != null && thread.getState() != Thread.State.TERMINATED) { - stopCalculationTrigger = true; - getAapsLogger().debug(LTag.AUTOSENS, "Stopping calculation thread: " + from); - while (thread.getState() != Thread.State.TERMINATED) { - SystemClock.sleep(100); - } - getAapsLogger().debug(LTag.AUTOSENS, "Calculation thread stopped: " + from); - } - } - - public void runCalculation(String from, long end, boolean bgDataReload, boolean limitDataToOldestAvailable, Event cause) { - getAapsLogger().debug(LTag.AUTOSENS, "Starting calculation thread: " + from + " to " + dateUtil.dateAndTimeAndSecondsString(end)); - if (thread == null || thread.getState() == Thread.State.TERMINATED) { - if (sensitivityOref1Plugin.isEnabled()) - thread = new IobCobOref1Thread(injector, this, treatmentsPlugin, from, end, bgDataReload, limitDataToOldestAvailable, cause); - else - thread = new IobCobThread(injector, this, treatmentsPlugin, from, end, bgDataReload, limitDataToOldestAvailable, cause); - thread.start(); - } - } - - // When historical data is changed (comming from NS etc) finished calculations after this date must be invalidated - private void newHistoryData(EventNewHistoryData ev, boolean bgDataReload) { - //log.debug("Locking onNewHistoryData"); - stopCalculation("onEventNewHistoryData"); - synchronized (dataLock) { - // clear up 5 min back for proper COB calculation - long time = ev.getTime() - 5 * 60 * 1000L; - getAapsLogger().debug(LTag.AUTOSENS, "Invalidating cached data to: " + dateUtil.dateAndTimeAndSecondsString(time)); - for (int index = iobTable.size() - 1; index >= 0; index--) { - if (iobTable.keyAt(index) > time) { - getAapsLogger().debug(LTag.AUTOSENS, "Removing from iobTable: " + dateUtil.dateAndTimeAndSecondsString(iobTable.keyAt(index))); - iobTable.removeAt(index); - } else { - break; - } - } - for (int index = absIobTable.size() - 1; index >= 0; index--) { - if (absIobTable.keyAt(index) > time) { - getAapsLogger().debug(LTag.AUTOSENS, "Removing from absIobTable: " + dateUtil.dateAndTimeAndSecondsString(absIobTable.keyAt(index))); - absIobTable.removeAt(index); - } else { - break; - } - } - for (int index = autosensDataTable.size() - 1; index >= 0; index--) { - if (autosensDataTable.keyAt(index) > time) { - getAapsLogger().debug(LTag.AUTOSENS, "Removing from autosensDataTable: " + dateUtil.dateAndTimeAndSecondsString(autosensDataTable.keyAt(index))); - autosensDataTable.removeAt(index); - } else { - break; - } - } - for (int index = basalDataTable.size() - 1; index >= 0; index--) { - if (basalDataTable.keyAt(index) > time) { - getAapsLogger().debug(LTag.AUTOSENS, "Removing from basalDataTable: " + dateUtil.dateAndTimeAndSecondsString(basalDataTable.keyAt(index))); - basalDataTable.removeAt(index); - } else { - break; - } - } - } - runCalculation("onEventNewHistoryData", System.currentTimeMillis(), bgDataReload, true, ev); - //log.debug("Releasing onNewHistoryData"); - } - - public void clearCache() { - synchronized (dataLock) { - getAapsLogger().debug(LTag.AUTOSENS, "Clearing cached data."); - iobTable = new LongSparseArray<>(); - autosensDataTable = new LongSparseArray<>(); - basalDataTable = new LongSparseArray<>(); - } - } - - /* - * Return last BgReading from database or null if db is empty - */ - @Nullable - public GlucoseValue lastBg() { - List bgList = getBgReadings(); - - if (bgList == null) - return null; - - for (int i = 0; i < bgList.size(); i++) - if (bgList.get(i).getValue() >= 39) - return bgList.get(i); - return null; - } - - /* - * Return bg reading if not old ( <9 min ) - * or null if older - */ - @Nullable - public GlucoseValue actualBg() { - GlucoseValue lastBg = lastBg(); - - if (lastBg == null) - return null; - - if (lastBg.getTimestamp() > System.currentTimeMillis() - 9 * 60 * 1000) - return lastBg; - - return null; - } - - - // From https://gist.github.com/IceCreamYou/6ffa1b18c4c8f6aeaad2 - // Returns the value at a given percentile in a sorted numeric array. - // "Linear interpolation between closest ranks" method - public static double percentile(Double[] arr, double p) { - if (arr.length == 0) return 0; - if (p <= 0) return arr[0]; - if (p >= 1) return arr[arr.length - 1]; - - double index = arr.length * p, - lower = Math.floor(index), - upper = lower + 1, - weight = index % 1; - - if (upper >= arr.length) return arr[(int) lower]; - return arr[(int) lower] * (1 - weight) + arr[(int) upper] * weight; - } -} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.kt new file mode 100644 index 0000000000..de1c931ccb --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.kt @@ -0,0 +1,799 @@ +package info.nightscout.androidaps.plugins.iob.iobCobCalculator + +import android.os.SystemClock +import androidx.collection.LongSparseArray +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.database.AppRepository +import info.nightscout.androidaps.database.entities.GlucoseValue +import info.nightscout.androidaps.db.TemporaryBasal +import info.nightscout.androidaps.events.* +import info.nightscout.androidaps.interfaces.* +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.bus.RxBusWrapper +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryBgData +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData +import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin +import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin +import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.DecimalFormatter +import info.nightscout.androidaps.utils.FabricPrivacy +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.JSONArray +import java.util.concurrent.TimeUnit +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.math.abs +import kotlin.math.floor +import kotlin.math.max +import kotlin.math.roundToLong + +@Singleton +open class IobCobCalculatorPlugin @Inject constructor( + injector: HasAndroidInjector, + aapsLogger: AAPSLogger, + private val aapsSchedulers: AapsSchedulers, + private val rxBus: RxBusWrapper, + private val sp: SP, + resourceHelper: ResourceHelper, + private val profileFunction: ProfileFunction, + private val activePlugin: ActivePluginProvider, + private val treatmentsPlugin: TreatmentsPlugin, + private val sensitivityOref1Plugin: SensitivityOref1Plugin, + private val sensitivityAAPSPlugin: SensitivityAAPSPlugin, + private val sensitivityWeightedAveragePlugin: SensitivityWeightedAveragePlugin, + private val fabricPrivacy: FabricPrivacy, + private val dateUtil: DateUtil, + private val repository: AppRepository +) : PluginBase(PluginDescription() + .mainType(PluginType.GENERAL) + .pluginName(R.string.iobcobcalculator) + .showInList(false) + .neverVisible(true) + .alwaysEnabled(true), + aapsLogger, resourceHelper, injector +), IobCobCalculatorInterface { + + private val disposable = CompositeDisposable() + private var iobTable = LongSparseArray() // oldest at index 0 + private var absIobTable = LongSparseArray() // oldest at index 0, absolute insulin in the body + private var autosensDataTable = LongSparseArray() // oldest at index 0 + private var basalDataTable = LongSparseArray() // oldest at index 0 + @Volatile var bgReadings: List = listOf() // newest at index 0 + @Volatile var bucketedData: MutableList? = null + + // we need to make sure that bucketed_data will always have the same timestamp for correct use of cached values + // once referenceTime != null all bucketed data should be (x * 5min) from referenceTime + var referenceTime: Long = -1 + private var lastUsed5minCalculation: Boolean? = null // true if used 5min bucketed data + val dataLock = Any() + var stopCalculationTrigger = false + private var thread: Thread? = null + + override fun onStart() { + super.onStart() + // EventConfigBuilderChange + disposable.add(rxBus + .toObservable(EventConfigBuilderChange::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ event -> + stopCalculation("onEventConfigBuilderChange") + synchronized(dataLock) { + aapsLogger.debug(LTag.AUTOSENS, "Invalidating cached data because of configuration change.") + resetData() + } + runCalculation("onEventConfigBuilderChange", System.currentTimeMillis(), bgDataReload = false, limitDataToOldestAvailable = true, cause = event) + }, fabricPrivacy::logException) + ) + // EventNewBasalProfile + disposable.add(rxBus + .toObservable(EventNewBasalProfile::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ event -> + stopCalculation("onNewProfile") + synchronized(dataLock) { + aapsLogger.debug(LTag.AUTOSENS, "Invalidating cached data because of new profile.") + resetData() + } + runCalculation("onNewProfile", System.currentTimeMillis(), bgDataReload = false, limitDataToOldestAvailable = true, cause = event) + }, fabricPrivacy::logException) + ) + // EventNewBG .... cannot be used for invalidating because only event with last BG is fired + disposable.add(rxBus + .toObservable(EventNewBG::class.java) + .observeOn(aapsSchedulers.io) + .debounce(1L, TimeUnit.SECONDS) + .subscribe({ event -> + stopCalculation("onEventNewBG") + runCalculation("onEventNewBG", System.currentTimeMillis(), bgDataReload = true, limitDataToOldestAvailable = true, cause = event) + }, fabricPrivacy::logException) + ) + // EventPreferenceChange + disposable.add(rxBus + .toObservable(EventPreferenceChange::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ event -> + if (event.isChanged(resourceHelper, R.string.key_openapsama_autosens_period) || + event.isChanged(resourceHelper, R.string.key_age) || + event.isChanged(resourceHelper, R.string.key_absorption_maxtime) || + event.isChanged(resourceHelper, R.string.key_openapsama_min_5m_carbimpact) || + event.isChanged(resourceHelper, R.string.key_absorption_cutoff) || + event.isChanged(resourceHelper, R.string.key_openapsama_autosens_max) || + event.isChanged(resourceHelper, R.string.key_openapsama_autosens_min) || + event.isChanged(resourceHelper, R.string.key_insulin_oref_peak)) { + stopCalculation("onEventPreferenceChange") + synchronized(dataLock) { + aapsLogger.debug(LTag.AUTOSENS, "Invalidating cached data because of preference change.") + resetData() + } + runCalculation("onEventPreferenceChange", System.currentTimeMillis(), bgDataReload = false, limitDataToOldestAvailable = true, cause = event) + } + }, fabricPrivacy::logException) + ) + // EventAppInitialized + disposable.add(rxBus + .toObservable(EventAppInitialized::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ event -> runCalculation("onEventAppInitialized", System.currentTimeMillis(), bgDataReload = true, limitDataToOldestAvailable = true, cause = event) }, fabricPrivacy::logException) + ) + // EventNewHistoryData + disposable.add(rxBus + .toObservable(EventNewHistoryData::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ event -> newHistoryData(event, false) }, fabricPrivacy::logException) + ) + // EventNewHistoryBgData + disposable.add(rxBus + .toObservable(EventNewHistoryBgData::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ event -> newHistoryData(EventNewHistoryData(event.timestamp), true) }, fabricPrivacy::logException) + ) + } + + override fun onStop() { + disposable.clear() + super.onStop() + } + + override fun getAutosensDataTable(): LongSparseArray { + return autosensDataTable + } + + fun adjustToReferenceTime(someTime: Long): Long { + if (referenceTime == -1L) { + referenceTime = someTime + return someTime + } + var diff = abs(someTime - referenceTime) + diff %= T.mins(5).msecs() + if (diff > T.mins(2).plus(T.secs(30)).msecs()) diff -= T.mins(5).msecs() + return someTime + diff + } + + fun loadBgData(to: Long) { + val profile = profileFunction.getProfile(to) + var dia = Constants.defaultDIA + if (profile != null) dia = profile.dia + val start = to - T.hours((24 + dia).toLong()).msecs() + if (DateUtil.isCloseToNow(to)) { + // if close to now expect there can be some readings with time in close future (caused by wrong time setting) + // so read all records + bgReadings = repository.compatGetBgReadingsDataFromTime(start, false).blockingGet() + aapsLogger.debug(LTag.AUTOSENS, "BG data loaded. Size: " + bgReadings.size + " Start date: " + dateUtil.dateAndTimeString(start)) + } else { + bgReadings = repository.compatGetBgReadingsDataFromTime(start, to, false).blockingGet() + aapsLogger.debug(LTag.AUTOSENS, "BG data loaded. Size: " + bgReadings.size + " Start date: " + dateUtil.dateAndTimeString(start) + " End date: " + dateUtil.dateAndTimeString(to)) + } + } + + val isAbout5minData: Boolean + get() { + synchronized(dataLock) { + if (bgReadings.size < 3) return true + + var totalDiff: Long = 0 + for (i in 1 until bgReadings.size) { + val bgTime = bgReadings[i].timestamp + val lastBgTime = bgReadings[i - 1].timestamp + var diff = lastBgTime - bgTime + diff %= T.mins(5).msecs() + if (diff > T.mins(2).plus(T.secs(30)).msecs()) diff -= T.mins(5).msecs() + totalDiff += diff + diff = abs(diff) + if (diff > T.secs(30).msecs()) { + aapsLogger.debug(LTag.AUTOSENS, "Interval detection: values: " + bgReadings.size + " diff: " + diff / 1000 + "[s] is5minData: " + false) + return false + } + } + val averageDiff = totalDiff / bgReadings.size / 1000 + val is5minData = averageDiff < 1 + aapsLogger.debug(LTag.AUTOSENS, "Interval detection: values: " + bgReadings.size + " averageDiff: " + averageDiff + "[s] is5minData: " + is5minData) + return is5minData + } + } + + private fun resetData() { + synchronized(dataLock) { + iobTable = LongSparseArray() + autosensDataTable = LongSparseArray() + basalDataTable = LongSparseArray() + absIobTable = LongSparseArray() + } + } + + fun createBucketedData() { + val fiveMinData = isAbout5minData + if (lastUsed5minCalculation != null && lastUsed5minCalculation != fiveMinData) { + // changing mode => clear cache + aapsLogger.debug("Invalidating cached data because of changed mode.") + resetData() + } + lastUsed5minCalculation = fiveMinData + if (isAbout5minData) createBucketedData5min() else createBucketedDataRecalculated() + } + + fun findNewer(time: Long): GlucoseValue? { + var lastFound = bgReadings[0] + if (lastFound.timestamp < time) return null + for (i in 1 until bgReadings.size) { + if (bgReadings[i].timestamp == time) return bgReadings[i] + if (bgReadings[i].timestamp > time) continue + lastFound = bgReadings[i - 1] + if (bgReadings[i].timestamp < time) break + } + return lastFound + } + + fun findOlder(time: Long): GlucoseValue? { + var lastFound = bgReadings[bgReadings.size - 1] + if (lastFound.timestamp > time) return null + for (i in bgReadings.size - 2 downTo 0) { + if (bgReadings[i].timestamp == time) return bgReadings[i] + if (bgReadings[i].timestamp < time) continue + lastFound = bgReadings[i + 1] + if (bgReadings[i].timestamp > time) break + } + return lastFound + } + + private fun createBucketedDataRecalculated() { + if (bgReadings.size < 3) { + bucketedData = null + return + } + bucketedData = ArrayList() + var currentTime = bgReadings[0].timestamp - bgReadings[0].timestamp % T.mins(5).msecs() + currentTime = adjustToReferenceTime(currentTime) + aapsLogger.debug("Adjusted time " + dateUtil.dateAndTimeAndSecondsString(currentTime)) + //log.debug("First reading: " + new Date(currentTime).toLocaleString()); + while (true) { + // test if current value is older than current time + val newer = findNewer(currentTime) + val older = findOlder(currentTime) + if (newer == null || older == null) break + if (older.timestamp == newer.timestamp) { // direct hit + bucketedData?.add(InMemoryGlucoseValue(newer)) + } else { + val bgDelta = newer.value - older.value + val timeDiffToNew = newer.timestamp - currentTime + val currentBg = newer.value - timeDiffToNew.toDouble() / (newer.timestamp - older.timestamp) * bgDelta + val newBgReading = InMemoryGlucoseValue(currentTime, currentBg.roundToLong().toDouble(), true) + bucketedData?.add(newBgReading) + //log.debug("BG: " + newBgReading.value + " (" + new Date(newBgReading.date).toLocaleString() + ") Prev: " + older.value + " (" + new Date(older.date).toLocaleString() + ") Newer: " + newer.value + " (" + new Date(newer.date).toLocaleString() + ")"); + } + currentTime -= T.mins(5).msecs() + } + } + + private fun createBucketedData5min() { + if (bgReadings.size < 3) { + bucketedData = null + return + } + val bData: MutableList = ArrayList() + bData.add(InMemoryGlucoseValue(bgReadings[0])) + aapsLogger.debug(LTag.AUTOSENS, "Adding. bgTime: " + DateUtil.toISOString(bgReadings[0].timestamp) + " lastBgTime: " + "none-first-value" + " " + bgReadings[0].toString()) + var j = 0 + for (i in 1 until bgReadings.size) { + val bgTime = bgReadings[i].timestamp + var lastBgTime = bgReadings[i - 1].timestamp + //log.error("Processing " + i + ": " + new Date(bgTime).toString() + " " + bgReadings.get(i).value + " Previous: " + new Date(lastBgTime).toString() + " " + bgReadings.get(i - 1).value); + check(!(bgReadings[i].value < 39 || bgReadings[i - 1].value < 39)) { "<39" } + var elapsedMinutes = (bgTime - lastBgTime) / (60 * 1000) + when { + abs(elapsedMinutes) > 8 -> { + // interpolate missing data points + var lastBg = bgReadings[i - 1].value + elapsedMinutes = abs(elapsedMinutes) + //console.error(elapsed_minutes); + var nextBgTime: Long + while (elapsedMinutes > 5) { + nextBgTime = lastBgTime - 5 * 60 * 1000 + j++ + val gapDelta = bgReadings[i].value - lastBg + //console.error(gapDelta, lastBg, elapsed_minutes); + val nextBg = lastBg + 5.0 / elapsedMinutes * gapDelta + val newBgReading = InMemoryGlucoseValue(nextBgTime, nextBg.roundToLong().toDouble(), true) + //console.error("Interpolated", bData[j]); + bData.add(newBgReading) + aapsLogger.debug(LTag.AUTOSENS, "Adding. bgTime: " + DateUtil.toISOString(bgTime) + " lastBgTime: " + DateUtil.toISOString(lastBgTime) + " " + newBgReading.toString()) + elapsedMinutes -= 5 + lastBg = nextBg + lastBgTime = nextBgTime + } + j++ + val newBgReading = InMemoryGlucoseValue(bgTime, bgReadings[i].value) + bData.add(newBgReading) + aapsLogger.debug(LTag.AUTOSENS, "Adding. bgTime: " + DateUtil.toISOString(bgTime) + " lastBgTime: " + DateUtil.toISOString(lastBgTime) + " " + newBgReading.toString()) + } + + abs(elapsedMinutes) > 2 -> { + j++ + val newBgReading = InMemoryGlucoseValue(bgTime, bgReadings[i].value) + bData.add(newBgReading) + aapsLogger.debug(LTag.AUTOSENS, "Adding. bgTime: " + DateUtil.toISOString(bgTime) + " lastBgTime: " + DateUtil.toISOString(lastBgTime) + " " + newBgReading.toString()) + } + + else -> { + bData[j].value = (bData[j].value + bgReadings[i].value) / 2 + //log.error("***** Average"); + } + } + } + + // Normalize bucketed data + val oldest = bData[bData.size - 1] + oldest.timestamp = adjustToReferenceTime(oldest.timestamp) + aapsLogger.debug("Adjusted time " + dateUtil.dateAndTimeAndSecondsString(oldest.timestamp)) + for (i in bData.size - 2 downTo 0) { + val current = bData[i] + val previous = bData[i + 1] + val mSecDiff = current.timestamp - previous.timestamp + val adjusted = (mSecDiff - T.mins(5).msecs()) / 1000 + aapsLogger.debug(LTag.AUTOSENS, "Adjusting bucketed data time. Current: " + dateUtil.dateAndTimeAndSecondsString(current.timestamp) + " to: " + dateUtil.dateAndTimeAndSecondsString(previous.timestamp + T.mins(5).msecs()) + " by " + adjusted + " sec") + if (abs(adjusted) > 90) { + // too big adjustment, fallback to non 5 min data + aapsLogger.debug(LTag.AUTOSENS, "Fallback to non 5 min data") + createBucketedDataRecalculated() + return + } + current.timestamp = previous.timestamp + T.mins(5).msecs() + } + aapsLogger.debug(LTag.AUTOSENS, "Bucketed data created. Size: " + bData.size) + bucketedData = bData + } + + fun calculateDetectionStart(from: Long, limitDataToOldestAvailable: Boolean): Long { + val profile = profileFunction.getProfile(from) + var dia = Constants.defaultDIA + if (profile != null) dia = profile.dia + val oldestDataAvailable = treatmentsPlugin.oldestDataAvailable() + val getBGDataFrom: Long + if (limitDataToOldestAvailable) { + getBGDataFrom = max(oldestDataAvailable, (from - T.hours(1).msecs() * (24 + dia)).toLong()) + if (getBGDataFrom == oldestDataAvailable) aapsLogger.debug(LTag.AUTOSENS, "Limiting data to oldest available temps: " + dateUtil.dateAndTimeAndSecondsString(oldestDataAvailable)) + } else getBGDataFrom = (from - T.hours(1).msecs() * (24 + dia)).toLong() + return getBGDataFrom + } + + fun calculateFromTreatmentsAndTempsSynchronized(time: Long, profile: Profile?): IobTotal { + synchronized(dataLock) { return calculateFromTreatmentsAndTemps(time, profile) } + } + + private fun calculateFromTreatmentsAndTempsSynchronized(time: Long, lastAutosensResult: AutosensResult, exercise_mode: Boolean, half_basal_exercise_target: Int, isTempTarget: Boolean): IobTotal { + synchronized(dataLock) { return calculateFromTreatmentsAndTemps(time, lastAutosensResult, exercise_mode, half_basal_exercise_target, isTempTarget) } + } + + fun calculateFromTreatmentsAndTemps(fromTime: Long, profile: Profile?): IobTotal { + val now = System.currentTimeMillis() + val time = roundUpTime(fromTime) + val cacheHit = iobTable[time] + if (time < now && cacheHit != null) { + //og.debug(">>> calculateFromTreatmentsAndTemps Cache hit " + new Date(time).toLocaleString()); + return cacheHit + } // else log.debug(">>> calculateFromTreatmentsAndTemps Cache miss " + new Date(time).toLocaleString()); + val bolusIob = treatmentsPlugin.getCalculationToTimeTreatments(time).round() + val basalIob = treatmentsPlugin.getCalculationToTimeTempBasals(time, true, now).round() + // OpenAPSSMB only + // Add expected zero temp basal for next 240 minutes + val basalIobWithZeroTemp = basalIob.copy() + val t = TemporaryBasal(injector) + .date(now + 60 * 1000L) + .duration(240) + .absolute(0.0) + if (t.date < time) { + val calc = t.iobCalc(time, profile) + basalIobWithZeroTemp.plus(calc) + } + basalIob.iobWithZeroTemp = IobTotal.combine(bolusIob, basalIobWithZeroTemp).round() + val iobTotal = IobTotal.combine(bolusIob, basalIob).round() + if (time < System.currentTimeMillis()) { + iobTable.put(time, iobTotal) + } + return iobTotal + } + + fun calculateAbsInsulinFromTreatmentsAndTempsSynchronized(fromTime: Long): IobTotal { + synchronized(dataLock) { + val now = System.currentTimeMillis() + val time = roundUpTime(fromTime) + val cacheHit = absIobTable[time] + if (time < now && cacheHit != null) { + //log.debug(">>> calculateFromTreatmentsAndTemps Cache hit " + new Date(time).toLocaleString()); + return cacheHit + } // else log.debug(">>> calculateFromTreatmentsAndTemps Cache miss " + new Date(time).toLocaleString()); + val bolusIob = treatmentsPlugin.getCalculationToTimeTreatments(time).round() + val basalIob = treatmentsPlugin.getAbsoluteIOBTempBasals(time).round() + val iobTotal = IobTotal.combine(bolusIob, basalIob).round() + if (time < System.currentTimeMillis()) { + absIobTable.put(time, iobTotal) + } + return iobTotal + } + } + + private fun calculateFromTreatmentsAndTemps(time: Long, lastAutosensResult: AutosensResult, exercise_mode: Boolean, half_basal_exercise_target: Int, isTempTarget: Boolean): IobTotal { + val now = DateUtil.now() + val bolusIob = treatmentsPlugin.getCalculationToTimeTreatments(time).round() + val basalIob = treatmentsPlugin.getCalculationToTimeTempBasals(time, now, lastAutosensResult, exercise_mode, half_basal_exercise_target, isTempTarget).round() + // OpenAPSSMB only + // Add expected zero temp basal for next 240 minutes + val basalIobWithZeroTemp = basalIob.copy() + val t = TemporaryBasal(injector) + .date(now + 60 * 1000L) + .duration(240) + .absolute(0.0) + if (t.date < time) { + val profile = profileFunction.getProfile(t.date) + if (profile != null) { + val calc = t.iobCalc(time, profile, lastAutosensResult, exercise_mode, half_basal_exercise_target, isTempTarget) + basalIobWithZeroTemp.plus(calc) + } + } + basalIob.iobWithZeroTemp = IobTotal.combine(bolusIob, basalIobWithZeroTemp).round() + return IobTotal.combine(bolusIob, basalIob).round() + } + + fun findPreviousTimeFromBucketedData(time: Long): Long? { + val bData = bucketedData ?: return null + for (index in bData.indices) { + if (bData[index].timestamp <= time) return bData[index].timestamp + } + return null + } + + fun getBasalData(profile: Profile, fromTime: Long): BasalData { + synchronized(dataLock) { + val now = System.currentTimeMillis() + val time = roundUpTime(fromTime) + var retVal = basalDataTable[time] + if (retVal == null) { + //log.debug(">>> getBasalData Cache miss " + new Date(time).toLocaleString()); + retVal = BasalData() + val tb = treatmentsPlugin.getTempBasalFromHistory(time) + retVal.basal = profile.getBasal(time) + if (tb != null) { + retVal.isTempBasalRunning = true + retVal.tempBasalAbsolute = tb.tempBasalConvertedToAbsolute(time, profile) + } else { + retVal.isTempBasalRunning = false + retVal.tempBasalAbsolute = retVal.basal + } + if (time < now) { + basalDataTable.append(time, retVal) + } + } //else log.debug(">>> getBasalData Cache hit " + new Date(time).toLocaleString()); + return retVal + } + } + + override fun getAutosensData(fromTime: Long): AutosensData? { + var time = fromTime + synchronized(dataLock) { + val now = System.currentTimeMillis() + if (time > now) { + return null + } + val previous = findPreviousTimeFromBucketedData(time) ?: return null + time = roundUpTime(previous) + return autosensDataTable[time] + } + } + + fun getLastAutosensDataSynchronized(reason: String): AutosensData? { + if (thread?.isAlive == true) { + aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA is waiting for calculation thread: $reason") + try { + thread?.join(5000) + } catch (ignored: InterruptedException) { + } + aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA finished waiting for calculation thread: $reason") + } + synchronized(dataLock) { return getLastAutosensData(reason) } + } + + fun getCobInfo(_synchronized: Boolean, reason: String): CobInfo { + val autosensData = if (_synchronized) getLastAutosensDataSynchronized(reason) else getLastAutosensData(reason) + var displayCob: Double? = null + var futureCarbs = 0.0 + val now = DateUtil.now() + val treatments = treatmentsPlugin.treatmentsFromHistory + if (autosensData != null) { + displayCob = autosensData.cob + for (treatment in treatments) { + if (!treatment.isValid) continue + if (roundUpTime(treatment.date) > roundUpTime(autosensData.time) && treatment.date <= now && treatment.carbs > 0) { + displayCob += treatment.carbs + } + } + } + for (treatment in treatments) { + if (!treatment.isValid) continue + if (treatment.date > now && treatment.carbs > 0) { + futureCarbs += treatment.carbs + } + } + return CobInfo(displayCob, futureCarbs) + } + + fun slowAbsorptionPercentage(timeInMinutes: Int): Double { + var sum = 0.0 + var count = 0 + val valuesToProcess = timeInMinutes / 5 + synchronized(dataLock) { + var i = autosensDataTable.size() - 1 + while (i >= 0 && count < valuesToProcess) { + if (autosensDataTable.valueAt(i).failoverToMinAbsorbtionRate) sum++ + count++ + i-- + } + } + return sum / count + } + + fun getLastAutosensData(reason: String): AutosensData? { + if (autosensDataTable.size() < 1) { + aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA null: autosensDataTable empty ($reason)") + return null + } + val data: AutosensData? = try { + autosensDataTable.valueAt(autosensDataTable.size() - 1) + } catch (e: Exception) { + // data can be processed on the background + // in this rare case better return null and do not block UI + // APS plugin should use getLastAutosensDataSynchronized where the blocking is not an issue + aapsLogger.error("AUTOSENSDATA null: Exception caught ($reason)") + return null + } + if (data == null) { + aapsLogger.error("AUTOSENSDATA null: data==null") + return null + } + return if (data.time < System.currentTimeMillis() - 11 * 60 * 1000) { + aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA null: data is old (" + reason + ") size()=" + autosensDataTable.size() + " lastData=" + dateUtil.dateAndTimeAndSecondsString(data.time)) + null + } else { + aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA ($reason) $data") + data + } + } + + override fun lastDataTime(): String { + return if (autosensDataTable.size() > 0) dateUtil.dateAndTimeAndSecondsString(autosensDataTable.valueAt(autosensDataTable.size() - 1).time) else "autosensDataTable empty" + } + + val mealData: MealData + get() { + val result = MealData() + val profile = profileFunction.getProfile() ?: return result + val now = System.currentTimeMillis() + val diaAgo = now - java.lang.Double.valueOf(profile.dia * T.hours(1).msecs()).toLong() + val maxAbsorptionHours: Double = if (sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled()) { + sp.getDouble(R.string.key_absorption_maxtime, Constants.DEFAULT_MAX_ABSORPTION_TIME) + } else { + sp.getDouble(R.string.key_absorption_cutoff, Constants.DEFAULT_MAX_ABSORPTION_TIME) + } + val absorptionTimeAgo = now - java.lang.Double.valueOf(maxAbsorptionHours * T.hours(1).msecs()).toLong() + val treatments = treatmentsPlugin.treatmentsFromHistory + for (treatment in treatments) { + if (!treatment.isValid) continue + val t = treatment.date + if (t in (diaAgo + 1)..now) { + if (treatment.insulin > 0 && treatment.mealBolus) { + result.boluses += treatment.insulin + } + } + if (t in (absorptionTimeAgo + 1)..now) { + if (treatment.carbs >= 1) { + result.carbs += treatment.carbs + if (t > result.lastCarbTime) result.lastCarbTime = t + } + } + } + val autosensData = getLastAutosensDataSynchronized("getMealData()") + if (autosensData != null) { + result.mealCOB = autosensData.cob + result.slopeFromMinDeviation = autosensData.slopeFromMinDeviation + result.slopeFromMaxDeviation = autosensData.slopeFromMaxDeviation + result.usedMinCarbsImpact = autosensData.usedMinCarbsImpact + } + result.lastBolusTime = treatmentsPlugin.lastBolusTime + return result + } + + override fun calculateIobArrayInDia(profile: Profile): Array { + // predict IOB out to DIA plus 30m + var time = System.currentTimeMillis() + time = roundUpTime(time) + val len = ((profile.dia * 60 + 30) / 5).toInt() + val array = Array(len) { IobTotal(0) } + for ((pos, i) in (0 until len).withIndex()) { + val t = time + i * 5 * 60000 + val iob = calculateFromTreatmentsAndTempsSynchronized(t, profile) + array[pos] = iob + } + return array + } + + fun calculateIobArrayForSMB(lastAutosensResult: AutosensResult, exercise_mode: Boolean, half_basal_exercise_target: Int, isTempTarget: Boolean): Array { + // predict IOB out to DIA plus 30m + val now = DateUtil.now() + val len = 4 * 60 / 5 + val array = Array(len) { IobTotal(0) } + for ((pos, i) in (0 until len).withIndex()) { + val t = now + i * 5 * 60000 + val iob = calculateFromTreatmentsAndTempsSynchronized(t, lastAutosensResult, exercise_mode, half_basal_exercise_target, isTempTarget) + array[pos] = iob + } + return array + } + + fun iobArrayToString(array: Array): String { + val sb = StringBuilder() + sb.append("[") + for (i in array) { + sb.append(DecimalFormatter.to2Decimal(i.iob)) + sb.append(", ") + } + sb.append("]") + return sb.toString() + } + + fun detectSensitivityWithLock(fromTime: Long, toTime: Long): AutosensResult { + synchronized(dataLock) { return activePlugin.activeSensitivity.detectSensitivity(this, fromTime, toTime) } + } + + fun stopCalculation(from: String) { + if (thread?.state != Thread.State.TERMINATED) { + stopCalculationTrigger = true + aapsLogger.debug(LTag.AUTOSENS, "Stopping calculation thread: $from") + while (thread?.state != Thread.State.TERMINATED) { + SystemClock.sleep(100) + } + aapsLogger.debug(LTag.AUTOSENS, "Calculation thread stopped: $from") + } + } + + fun runCalculation(from: String, end: Long, bgDataReload: Boolean, limitDataToOldestAvailable: Boolean, cause: Event) { + aapsLogger.debug(LTag.AUTOSENS, "Starting calculation thread: " + from + " to " + dateUtil.dateAndTimeAndSecondsString(end)) + if (thread == null || thread?.state == Thread.State.TERMINATED) { + thread = if (sensitivityOref1Plugin.isEnabled()) IobCobOref1Thread(injector, this, treatmentsPlugin, from, end, bgDataReload, limitDataToOldestAvailable, cause) else IobCobThread(injector, this, treatmentsPlugin, from, end, bgDataReload, limitDataToOldestAvailable, cause) + thread?.start() + } + } + + // When historical data is changed (coming from NS etc) finished calculations after this date must be invalidated + private fun newHistoryData(ev: EventNewHistoryData, bgDataReload: Boolean) { + //log.debug("Locking onNewHistoryData"); + stopCalculation("onEventNewHistoryData") + synchronized(dataLock) { + + // clear up 5 min back for proper COB calculation + val time = ev.time - 5 * 60 * 1000L + aapsLogger.debug(LTag.AUTOSENS, "Invalidating cached data to: " + dateUtil.dateAndTimeAndSecondsString(time)) + for (index in iobTable.size() - 1 downTo 0) { + if (iobTable.keyAt(index) > time) { + aapsLogger.debug(LTag.AUTOSENS, "Removing from iobTable: " + dateUtil.dateAndTimeAndSecondsString(iobTable.keyAt(index))) + iobTable.removeAt(index) + } else { + break + } + } + for (index in absIobTable.size() - 1 downTo 0) { + if (absIobTable.keyAt(index) > time) { + aapsLogger.debug(LTag.AUTOSENS, "Removing from absIobTable: " + dateUtil.dateAndTimeAndSecondsString(absIobTable.keyAt(index))) + absIobTable.removeAt(index) + } else { + break + } + } + for (index in autosensDataTable.size() - 1 downTo 0) { + if (autosensDataTable.keyAt(index) > time) { + aapsLogger.debug(LTag.AUTOSENS, "Removing from autosensDataTable: " + dateUtil.dateAndTimeAndSecondsString(autosensDataTable.keyAt(index))) + autosensDataTable.removeAt(index) + } else { + break + } + } + for (index in basalDataTable.size() - 1 downTo 0) { + if (basalDataTable.keyAt(index) > time) { + aapsLogger.debug(LTag.AUTOSENS, "Removing from basalDataTable: " + dateUtil.dateAndTimeAndSecondsString(basalDataTable.keyAt(index))) + basalDataTable.removeAt(index) + } else { + break + } + } + } + runCalculation("onEventNewHistoryData", System.currentTimeMillis(), bgDataReload, true, ev) + //log.debug("Releasing onNewHistoryData"); + } + + fun clearCache() { + synchronized(dataLock) { + aapsLogger.debug(LTag.AUTOSENS, "Clearing cached data.") + iobTable = LongSparseArray() + autosensDataTable = LongSparseArray() + basalDataTable = LongSparseArray() + } + } + + /* + * Return last BgReading from database or null if db is empty + */ + fun lastBg(): GlucoseValue? { + val bgList = bgReadings + for (i in bgList.indices) if (bgList[i].value >= 39) return bgList[i] + return null + } + + /* + * Return bg reading if not old ( <9 min ) + * or null if older + */ + fun actualBg(): GlucoseValue? { + val lastBg = lastBg() ?: return null + return if (lastBg.timestamp > System.currentTimeMillis() - 9 * 60 * 1000) lastBg else null + } + + companion object { + + // roundup to whole minute + fun roundUpTime(time: Long): Long { + return if (time % 60000 == 0L) time else (time / 60000 + 1) * 60000 + } + + fun convertToJSONArray(iobArray: Array): JSONArray { + val array = JSONArray() + for (i in iobArray.indices) { + array.put(iobArray[i].determineBasalJson()) + } + return array + } + + // From https://gist.github.com/IceCreamYou/6ffa1b18c4c8f6aeaad2 + // Returns the value at a given percentile in a sorted numeric array. + // "Linear interpolation between closest ranks" method + fun percentile(arr: Array, p: Double): Double { + if (arr.isEmpty()) return 0.0 + if (p <= 0) return arr[0] + if (p >= 1) return arr[arr.size - 1] + val index = arr.size * p + val lower = floor(index) + val upper = lower + 1 + val weight = index % 1 + return if (upper >= arr.size) arr[lower.toInt()] else arr[lower.toInt()] * (1 - weight) + arr[upper.toInt()] * weight + } + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.java b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.java deleted file mode 100644 index ad68c3acc6..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.java +++ /dev/null @@ -1,399 +0,0 @@ -package info.nightscout.androidaps.plugins.iob.iobCobCalculator; - -import android.content.Context; -import android.os.PowerManager; -import android.os.SystemClock; - -import androidx.collection.LongSparseArray; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.List; - -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.Profile; -import info.nightscout.androidaps.db.TempTarget; -import info.nightscout.androidaps.db.Treatment; -import info.nightscout.androidaps.events.Event; -import info.nightscout.androidaps.interfaces.ProfileFunction; -import info.nightscout.androidaps.logging.AAPSLogger; -import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults; -import info.nightscout.androidaps.plugins.bus.RxBusWrapper; -import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; -import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensBgLoaded; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress; -import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin; -import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin; -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.DecimalFormatter; -import info.nightscout.androidaps.utils.FabricPrivacy; -import info.nightscout.androidaps.utils.MidnightTime; -import info.nightscout.androidaps.utils.Profiler; -import info.nightscout.androidaps.utils.T; -import info.nightscout.androidaps.utils.buildHelper.BuildHelper; -import info.nightscout.androidaps.utils.resources.ResourceHelper; -import info.nightscout.androidaps.utils.sharedPreferences.SP; - -import static info.nightscout.androidaps.utils.DateUtil.now; -import static java.util.Calendar.MINUTE; - -/** - * Created by mike on 23.01.2018. - */ - -public class IobCobOref1Thread extends Thread { - private final Event cause; - - @Inject AAPSLogger aapsLogger; - @Inject SP sp; - @Inject RxBusWrapper rxBus; - @Inject ResourceHelper resourceHelper; - @Inject ProfileFunction profileFunction; - @Inject Context context; - @Inject SensitivityAAPSPlugin sensitivityAAPSPlugin; - @Inject SensitivityWeightedAveragePlugin sensitivityWeightedAveragePlugin; - @Inject BuildHelper buildHelper; - @Inject Profiler profiler; - @Inject FabricPrivacy fabricPrivacy; - @Inject DateUtil dateUtil; - - private final HasAndroidInjector injector; - private final IobCobCalculatorPlugin iobCobCalculatorPlugin; // cannot be injected : HistoryBrowser uses different instance - private final TreatmentsPlugin treatmentsPlugin; // cannot be injected : HistoryBrowser uses different instance - private final boolean bgDataReload; - private final boolean limitDataToOldestAvailable; - private final String from; - private final long end; - - private PowerManager.WakeLock mWakeLock; - - IobCobOref1Thread(HasAndroidInjector injector, IobCobCalculatorPlugin iobCobCalculatorPlugin, TreatmentsPlugin treatmentsPlugin, String from, long end, boolean bgDataReload, boolean limitDataToOldestAvailable, Event cause) { - super(); - injector.androidInjector().inject(this); - this.injector = injector; - this.iobCobCalculatorPlugin = iobCobCalculatorPlugin; - this.treatmentsPlugin = treatmentsPlugin; - - this.bgDataReload = bgDataReload; - this.limitDataToOldestAvailable = limitDataToOldestAvailable; - this.from = from; - this.cause = cause; - this.end = end; - - PowerManager powerManager = (PowerManager) context.getApplicationContext().getSystemService(Context.POWER_SERVICE); - if (powerManager != null) - mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, resourceHelper.gs(R.string.app_name) + ":iobCobThread"); - } - - @Override - public final void run() { - long start = DateUtil.now(); - if (mWakeLock != null) - mWakeLock.acquire(T.mins(10).msecs()); - try { - aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread started: " + from); - if (!profileFunction.isProfileValid("IobCobThread")) { - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No profile): " + from); - return; // app still initializing - } - //log.debug("Locking calculateSensitivityData"); - - long oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable); - - synchronized (iobCobCalculatorPlugin.getDataLock()) { - if (bgDataReload) { - iobCobCalculatorPlugin.loadBgData(end); - iobCobCalculatorPlugin.createBucketedData(); - rxBus.send(new EventAutosensBgLoaded(cause)); - } - List bucketed_data = iobCobCalculatorPlugin.getBucketedData(); - LongSparseArray autosensDataTable = iobCobCalculatorPlugin.getAutosensDataTable(); - - if (bucketed_data == null || bucketed_data.size() < 3) { - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No bucketed data available): " + from); - return; - } - - long prevDataTime = IobCobCalculatorPlugin.roundUpTime(bucketed_data.get(bucketed_data.size() - 3).getTimestamp()); - aapsLogger.debug(LTag.AUTOSENS, "Prev data time: " + dateUtil.dateAndTimeString(prevDataTime)); - AutosensData previous = autosensDataTable.get(prevDataTime); - // start from oldest to be able sub cob - for (int i = bucketed_data.size() - 4; i >= 0; i--) { - String progress = i + (buildHelper.isDev() ? " (" + from + ")" : ""); - rxBus.send(new EventIobCalculationProgress(progress)); - - if (iobCobCalculatorPlugin.stopCalculationTrigger) { - iobCobCalculatorPlugin.stopCalculationTrigger = false; - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): " + from); - return; - } - // check if data already exists - long bgTime = bucketed_data.get(i).getTimestamp(); - bgTime = IobCobCalculatorPlugin.roundUpTime(bgTime); - if (bgTime > IobCobCalculatorPlugin.roundUpTime(now())) - continue; - - AutosensData existing; - if ((existing = autosensDataTable.get(bgTime)) != null) { - previous = existing; - continue; - } - - Profile profile = profileFunction.getProfile(bgTime); - if (profile == null) { - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (no profile): " + from); - return; // profile not set yet - } - - aapsLogger.debug(LTag.AUTOSENS, "Processing calculation thread: " + from + " (" + i + "/" + bucketed_data.size() + ")"); - - double sens = profile.getIsfMgdl(bgTime); - - AutosensData autosensData = new AutosensData(injector); - autosensData.time = bgTime; - if (previous != null) - autosensData.activeCarbsList = previous.cloneCarbsList(); - else - autosensData.activeCarbsList = new ArrayList<>(); - - //console.error(bgTime , bucketed_data[i].glucose); - double bg; - double avgDelta; - double delta; - bg = bucketed_data.get(i).getValue(); - if (bg < 39 || bucketed_data.get(i + 3).getValue() < 39) { - aapsLogger.error("! value < 39"); - continue; - } - autosensData.bg = bg; - delta = (bg - bucketed_data.get(i + 1).getValue()); - avgDelta = (bg - bucketed_data.get(i + 3).getValue()) / 3; - - IobTotal iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTemps(bgTime, profile); - - double bgi = -iob.activity * sens * 5; - double deviation = delta - bgi; - double avgDeviation = Math.round((avgDelta - bgi) * 1000) / 1000d; - - double slopeFromMaxDeviation = 0; - double slopeFromMinDeviation = 999; - double maxDeviation = 0; - double minDeviation = 999; - - // https://github.com/openaps/oref0/blob/master/lib/determine-basal/cob-autosens.js#L169 - if (i < bucketed_data.size() - 16) { // we need 1h of data to calculate minDeviationSlope - long hourago = bgTime + 10 * 1000 - 60 * 60 * 1000L; - AutosensData hourAgoData = iobCobCalculatorPlugin.getAutosensData(hourago); - if (hourAgoData != null) { - int initialIndex = autosensDataTable.indexOfKey(hourAgoData.time); - aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketed_data.size() + " i=" + i + " hourAgoData=" + hourAgoData.toString()); - int past = 1; - try { - for (; past < 12; past++) { - AutosensData ad = autosensDataTable.valueAt(initialIndex + past); - aapsLogger.debug(LTag.AUTOSENS, ">>>>> past=" + past + " ad=" + (ad != null ? ad.toString() : null)); - if (ad == null) { - aapsLogger.debug(LTag.AUTOSENS, autosensDataTable.toString()); - aapsLogger.debug(LTag.AUTOSENS, bucketed_data.toString()); - aapsLogger.debug(LTag.AUTOSENS, iobCobCalculatorPlugin.getBgReadings().toString()); - Notification notification = new Notification(Notification.SENDLOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW); - rxBus.send(new EventNewNotification(notification)); - sp.putBoolean("log_AUTOSENS", true); - break; - } - // let it here crash on NPE to get more data as i cannot reproduce this bug - double deviationSlope = (ad.avgDeviation - avgDeviation) / (ad.time - bgTime) * 1000 * 60 * 5; - if (ad.avgDeviation > maxDeviation) { - slopeFromMaxDeviation = Math.min(0, deviationSlope); - maxDeviation = ad.avgDeviation; - } - if (ad.avgDeviation < minDeviation) { - slopeFromMinDeviation = Math.max(0, deviationSlope); - minDeviation = ad.avgDeviation; - } - - //if (Config.isEnabled(L.AUTOSENS)) - // log.debug("Deviations: " + new Date(bgTime) + new Date(ad.time) + " avgDeviation=" + avgDeviation + " deviationSlope=" + deviationSlope + " slopeFromMaxDeviation=" + slopeFromMaxDeviation + " slopeFromMinDeviation=" + slopeFromMinDeviation); - } - } catch (Exception e) { - aapsLogger.error("Unhandled exception", e); - fabricPrivacy.logException(e); - aapsLogger.debug(autosensDataTable.toString()); - aapsLogger.debug(bucketed_data.toString()); - aapsLogger.debug(iobCobCalculatorPlugin.getBgReadings().toString()); - Notification notification = new Notification(Notification.SENDLOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW); - rxBus.send(new EventNewNotification(notification)); - sp.putBoolean("log_AUTOSENS", true); - break; - } - } else { - aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketed_data.size() + " i=" + i + " hourAgoData=" + "null"); - } - } - - List recentCarbTreatments = treatmentsPlugin.getCarbTreatments5MinBackFromHistory(bgTime); - for (Treatment recentCarbTreatment : recentCarbTreatments) { - autosensData.carbsFromBolus += recentCarbTreatment.carbs; - boolean isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled(); - autosensData.activeCarbsList.add(autosensData.new CarbsInPast(recentCarbTreatment, isAAPSOrWeighted)); - autosensData.pastSensitivity += "[" + DecimalFormatter.to0Decimal(recentCarbTreatment.carbs) + "g]"; - } - - - // if we are absorbing carbs - if (previous != null && previous.cob > 0) { - // calculate sum of min carb impact from all active treatments - double totalMinCarbsImpact = 0d; -// if (SensitivityAAPSPlugin.getPlugin().isEnabled(PluginType.SENSITIVITY) || SensitivityWeightedAveragePlugin.getPlugin().isEnabled(PluginType.SENSITIVITY)) { - //when the impact depends on a max time, sum them up as smaller carb sizes make them smaller -// for (int ii = 0; ii < autosensData.activeCarbsList.size(); ++ii) { -// AutosensData.CarbsInPast c = autosensData.activeCarbsList.get(ii); -// totalMinCarbsImpact += c.min5minCarbImpact; -// } -// } else { - //Oref sensitivity - totalMinCarbsImpact = sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact); -// } - - // figure out how many carbs that represents - // but always assume at least 3mg/dL/5m (default) absorption per active treatment - double ci = Math.max(deviation, totalMinCarbsImpact); - if (ci != deviation) - autosensData.failoverToMinAbsorbtionRate = true; - autosensData.absorbed = ci * profile.getIc(bgTime) / sens; - // and add that to the running total carbsAbsorbed - autosensData.cob = Math.max(previous.cob - autosensData.absorbed, 0d); - autosensData.mealCarbs = previous.mealCarbs; - autosensData.substractAbosorbedCarbs(); - autosensData.usedMinCarbsImpact = totalMinCarbsImpact; - autosensData.absorbing = previous.absorbing; - autosensData.mealStartCounter = previous.mealStartCounter; - autosensData.type = previous.type; - autosensData.uam = previous.uam; - } - - boolean isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled(); - autosensData.removeOldCarbs(bgTime, isAAPSOrWeighted); - autosensData.cob += autosensData.carbsFromBolus; - autosensData.mealCarbs += autosensData.carbsFromBolus; - autosensData.deviation = deviation; - autosensData.bgi = bgi; - autosensData.delta = delta; - autosensData.avgDelta = avgDelta; - autosensData.avgDeviation = avgDeviation; - autosensData.slopeFromMaxDeviation = slopeFromMaxDeviation; - autosensData.slopeFromMinDeviation = slopeFromMinDeviation; - - - // If mealCOB is zero but all deviations since hitting COB=0 are positive, exclude from autosens - if (autosensData.cob > 0 || autosensData.absorbing || autosensData.mealCarbs > 0) { - autosensData.absorbing = deviation > 0; - // stop excluding positive deviations as soon as mealCOB=0 if meal has been absorbing for >5h - if (autosensData.mealStartCounter > 60 && autosensData.cob < 0.5) { - autosensData.absorbing = false; - } - if (!autosensData.absorbing && autosensData.cob < 0.5) { - autosensData.mealCarbs = 0; - } - // check previous "type" value, and if it wasn't csf, set a mealAbsorption start flag - if (!autosensData.type.equals("csf")) { -// process.stderr.write("("); - autosensData.mealStartCounter = 0; - } - autosensData.mealStartCounter++; - autosensData.type = "csf"; - } else { - // check previous "type" value, and if it was csf, set a mealAbsorption end flag - if (autosensData.type.equals("csf")) { -// process.stderr.write(")"); - } - - double currentBasal = profile.getBasal(bgTime); - // always exclude the first 45m after each carb entry - //if (iob.iob > currentBasal || uam ) { - if (iob.iob > 2 * currentBasal || autosensData.uam || autosensData.mealStartCounter < 9) { - autosensData.mealStartCounter++; - autosensData.uam = deviation > 0; - if (!autosensData.type.equals("uam")) { -// process.stderr.write("u("); - } - autosensData.type = "uam"; - } else { - if (autosensData.type.equals("uam")) { -// process.stderr.write(")"); - } - autosensData.type = "non-meal"; - } - } - - // Exclude meal-related deviations (carb absorption) from autosens - if (autosensData.type.equals("non-meal")) { - if (Math.abs(deviation) < Constants.DEVIATION_TO_BE_EQUAL) { - autosensData.pastSensitivity += "="; - autosensData.validDeviation = true; - } else if (deviation > 0) { - autosensData.pastSensitivity += "+"; - autosensData.validDeviation = true; - } else { - autosensData.pastSensitivity += "-"; - autosensData.validDeviation = true; - } - } else if (autosensData.type.equals("uam")) { - autosensData.pastSensitivity += "u"; - } else { - autosensData.pastSensitivity += "x"; - } - //log.debug("TIME: " + new Date(bgTime).toString() + " BG: " + bg + " SENS: " + sens + " DELTA: " + delta + " AVGDELTA: " + avgDelta + " IOB: " + iob.iob + " ACTIVITY: " + iob.activity + " BGI: " + bgi + " DEVIATION: " + deviation); - - // add an extra negative deviation if a high temptarget is running and exercise mode is set - // TODO AS-FIX - if (false && sp.getBoolean(R.string.key_high_temptarget_raises_sensitivity, SMBDefaults.high_temptarget_raises_sensitivity)) { - TempTarget tempTarget = treatmentsPlugin.getTempTargetFromHistory(bgTime); - if (tempTarget != null && tempTarget.target() >= 100) { - autosensData.extraDeviation.add(-(tempTarget.target() - 100) / 20); - } - } - - // add one neutral deviation every 2 hours to help decay over long exclusion periods - GregorianCalendar calendar = new GregorianCalendar(); - calendar.setTimeInMillis(bgTime); - int min = calendar.get(MINUTE); - int hours = calendar.get(Calendar.HOUR_OF_DAY); - if (min >= 0 && min < 5 && hours % 2 == 0) - autosensData.extraDeviation.add(0d); - - previous = autosensData; - if (bgTime < now()) - autosensDataTable.put(bgTime, autosensData); - aapsLogger.debug(LTag.AUTOSENS, "Running detectSensitivity from: " + dateUtil.dateAndTimeString(oldestTimeWithData) + " to: " + dateUtil.dateAndTimeString(bgTime) + " lastDataTime:" + iobCobCalculatorPlugin.lastDataTime()); - AutosensResult sensitivity = iobCobCalculatorPlugin.detectSensitivityWithLock(oldestTimeWithData, bgTime); - aapsLogger.debug(LTag.AUTOSENS, "Sensitivity result: " + sensitivity.toString()); - autosensData.autosensResult = sensitivity; - aapsLogger.debug(LTag.AUTOSENS, autosensData.toString()); - } - } - new Thread(() -> { - SystemClock.sleep(1000); - rxBus.send(new EventAutosensCalculationFinished(cause)); - }).start(); - } finally { - if (mWakeLock != null) - mWakeLock.release(); - rxBus.send(new EventIobCalculationProgress("")); - aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: " + from); - aapsLogger.debug(LTag.AUTOSENS, "Midnights: " + MidnightTime.log()); - profiler.log(LTag.AUTOSENS, "IobCobOref1Thread", start); - } - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.kt new file mode 100644 index 0000000000..5bb64109ae --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.kt @@ -0,0 +1,328 @@ +package info.nightscout.androidaps.plugins.iob.iobCobCalculator + +import android.content.Context +import android.os.PowerManager +import android.os.SystemClock +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.Constants +import info.nightscout.androidaps.R +import info.nightscout.androidaps.events.Event +import info.nightscout.androidaps.interfaces.ProfileFunction +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults +import info.nightscout.androidaps.plugins.bus.RxBusWrapper +import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification +import info.nightscout.androidaps.plugins.general.overview.notifications.Notification +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin.Companion.roundUpTime +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensBgLoaded +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress +import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin +import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.utils.* +import info.nightscout.androidaps.utils.buildHelper.BuildHelper +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.sharedPreferences.SP +import java.util.* +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min +import kotlin.math.roundToLong + +class IobCobOref1Thread internal constructor( + private val injector: HasAndroidInjector, + private val iobCobCalculatorPlugin: IobCobCalculatorPlugin, // cannot be injected : HistoryBrowser uses different instance + private val treatmentsPlugin: TreatmentsPlugin, // cannot be injected : HistoryBrowser uses different instance + private val from: String, + private val end: Long, + private val bgDataReload: Boolean, + private val limitDataToOldestAvailable: Boolean, + private val cause: Event +) : Thread() { + + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var sp: SP + @Inject lateinit var rxBus: RxBusWrapper + @Inject lateinit var resourceHelper: ResourceHelper + @Inject lateinit var profileFunction: ProfileFunction + @Inject lateinit var context: Context + @Inject lateinit var sensitivityAAPSPlugin: SensitivityAAPSPlugin + @Inject lateinit var sensitivityWeightedAveragePlugin: SensitivityWeightedAveragePlugin + @Inject lateinit var buildHelper: BuildHelper + @Inject lateinit var profiler: Profiler + @Inject lateinit var fabricPrivacy: FabricPrivacy + @Inject lateinit var dateUtil: DateUtil + + private var mWakeLock: PowerManager.WakeLock? = null + + init { + injector.androidInjector().inject(this) + mWakeLock = (context.applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, resourceHelper.gs(R.string.app_name) + ":iobCobThread") + } + + override fun run() { + val start = DateUtil.now() + mWakeLock?.acquire(T.mins(10).msecs()) + try { + aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread started: $from") + if (!profileFunction.isProfileValid("IobCobThread")) { + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No profile): $from") + return // app still initializing + } + //log.debug("Locking calculateSensitivityData"); + val oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable) + synchronized(iobCobCalculatorPlugin.dataLock) { + if (bgDataReload) { + iobCobCalculatorPlugin.loadBgData(end) + iobCobCalculatorPlugin.createBucketedData() + rxBus.send(EventAutosensBgLoaded(cause)) + } + val bucketedData = iobCobCalculatorPlugin.bucketedData + val autosensDataTable = iobCobCalculatorPlugin.autosensDataTable + if (bucketedData == null || bucketedData.size < 3) { + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No bucketed data available): $from") + return + } + val prevDataTime = roundUpTime(bucketedData[bucketedData.size - 3].timestamp) + aapsLogger.debug(LTag.AUTOSENS, "Prev data time: " + dateUtil.dateAndTimeString(prevDataTime)) + var previous = autosensDataTable[prevDataTime] + // start from oldest to be able sub cob + for (i in bucketedData.size - 4 downTo 0) { + val progress = i.toString() + if (buildHelper.isDev()) " ($from)" else "" + rxBus.send(EventIobCalculationProgress(progress)) + if (iobCobCalculatorPlugin.stopCalculationTrigger) { + iobCobCalculatorPlugin.stopCalculationTrigger = false + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): $from") + return + } + // check if data already exists + var bgTime = bucketedData[i].timestamp + bgTime = roundUpTime(bgTime) + if (bgTime > roundUpTime(DateUtil.now())) continue + var existing: AutosensData? + if (autosensDataTable[bgTime].also { existing = it } != null) { + previous = existing + continue + } + val profile = profileFunction.getProfile(bgTime) + if (profile == null) { + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (no profile): $from") + return // profile not set yet + } + aapsLogger.debug(LTag.AUTOSENS, "Processing calculation thread: " + from + " (" + i + "/" + bucketedData.size + ")") + val sens = profile.getIsfMgdl(bgTime) + val autosensData = AutosensData(injector) + autosensData.time = bgTime + if (previous != null) autosensData.activeCarbsList = previous.cloneCarbsList() else autosensData.activeCarbsList = ArrayList() + + //console.error(bgTime , bucketed_data[i].glucose); + var avgDelta: Double + var delta: Double + val bg: Double = bucketedData[i].value + if (bg < 39 || bucketedData[i + 3].value < 39) { + aapsLogger.error("! value < 39") + continue + } + autosensData.bg = bg + delta = bg - bucketedData[i + 1].value + avgDelta = (bg - bucketedData[i + 3].value) / 3 + val iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTemps(bgTime, profile) + val bgi = -iob.activity * sens * 5 + val deviation = delta - bgi + val avgDeviation = ((avgDelta - bgi) * 1000).roundToLong() / 1000.0 + var slopeFromMaxDeviation = 0.0 + var slopeFromMinDeviation = 999.0 + + // https://github.com/openaps/oref0/blob/master/lib/determine-basal/cob-autosens.js#L169 + if (i < bucketedData.size - 16) { // we need 1h of data to calculate minDeviationSlope + @Suppress("UNUSED_VARIABLE") var maxDeviation = 0.0 + @Suppress("UNUSED_VARIABLE") var minDeviation = 999.0 + val hourAgo = bgTime + 10 * 1000 - 60 * 60 * 1000L + val hourAgoData = iobCobCalculatorPlugin.getAutosensData(hourAgo) + if (hourAgoData != null) { + val initialIndex = autosensDataTable.indexOfKey(hourAgoData.time) + aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketedData.size + " i=" + i + " hourAgoData=" + hourAgoData.toString()) + var past = 1 + try { + while (past < 12) { + val ad = autosensDataTable.valueAt(initialIndex + past) + aapsLogger.debug(LTag.AUTOSENS, ">>>>> past=" + past + " ad=" + ad?.toString()) + if (ad == null) { + aapsLogger.debug(LTag.AUTOSENS, autosensDataTable.toString()) + aapsLogger.debug(LTag.AUTOSENS, bucketedData.toString()) + aapsLogger.debug(LTag.AUTOSENS, iobCobCalculatorPlugin.bgReadings.toString()) + val notification = Notification(Notification.SENDLOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW) + rxBus.send(EventNewNotification(notification)) + sp.putBoolean("log_AUTOSENS", true) + break + } + // let it here crash on NPE to get more data as i cannot reproduce this bug + val deviationSlope = (ad.avgDeviation - avgDeviation) / (ad.time - bgTime) * 1000 * 60 * 5 + if (ad.avgDeviation > maxDeviation) { + slopeFromMaxDeviation = min(0.0, deviationSlope) + maxDeviation = ad.avgDeviation + } + if (ad.avgDeviation < minDeviation) { + slopeFromMinDeviation = max(0.0, deviationSlope) + minDeviation = ad.avgDeviation + } + past++ + } + } catch (e: Exception) { + aapsLogger.error("Unhandled exception", e) + fabricPrivacy.logException(e) + aapsLogger.debug(autosensDataTable.toString()) + aapsLogger.debug(bucketedData.toString()) + aapsLogger.debug(iobCobCalculatorPlugin.bgReadings.toString()) + val notification = Notification(Notification.SENDLOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW) + rxBus.send(EventNewNotification(notification)) + sp.putBoolean("log_AUTOSENS", true) + break + } + } else { + aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketedData.size + " i=" + i + " hourAgoData=" + "null") + } + } + val recentCarbTreatments = treatmentsPlugin.getCarbTreatments5MinBackFromHistory(bgTime) + for (recentCarbTreatment in recentCarbTreatments) { + autosensData.carbsFromBolus += recentCarbTreatment.carbs + val isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled() + autosensData.activeCarbsList.add(autosensData.CarbsInPast(recentCarbTreatment, isAAPSOrWeighted)) + autosensData.pastSensitivity += "[" + DecimalFormatter.to0Decimal(recentCarbTreatment.carbs) + "g]" + } + + // if we are absorbing carbs + if (previous != null && previous.cob > 0) { + // calculate sum of min carb impact from all active treatments + val totalMinCarbsImpact = sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact) + + // figure out how many carbs that represents + // but always assume at least 3mg/dL/5m (default) absorption per active treatment + val ci = max(deviation, totalMinCarbsImpact) + if (ci != deviation) autosensData.failoverToMinAbsorbtionRate = true + autosensData.absorbed = ci * profile.getIc(bgTime) / sens + // and add that to the running total carbsAbsorbed + autosensData.cob = max(previous.cob - autosensData.absorbed, 0.0) + autosensData.mealCarbs = previous.mealCarbs + autosensData.substractAbosorbedCarbs() + autosensData.usedMinCarbsImpact = totalMinCarbsImpact + autosensData.absorbing = previous.absorbing + autosensData.mealStartCounter = previous.mealStartCounter + autosensData.type = previous.type + autosensData.uam = previous.uam + } + val isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled() + autosensData.removeOldCarbs(bgTime, isAAPSOrWeighted) + autosensData.cob += autosensData.carbsFromBolus + autosensData.mealCarbs += autosensData.carbsFromBolus + autosensData.deviation = deviation + autosensData.bgi = bgi + autosensData.delta = delta + autosensData.avgDelta = avgDelta + autosensData.avgDeviation = avgDeviation + autosensData.slopeFromMaxDeviation = slopeFromMaxDeviation + autosensData.slopeFromMinDeviation = slopeFromMinDeviation + + // If mealCOB is zero but all deviations since hitting COB=0 are positive, exclude from autosens + if (autosensData.cob > 0 || autosensData.absorbing || autosensData.mealCarbs > 0) { + autosensData.absorbing = deviation > 0 + // stop excluding positive deviations as soon as mealCOB=0 if meal has been absorbing for >5h + if (autosensData.mealStartCounter > 60 && autosensData.cob < 0.5) { + autosensData.absorbing = false + } + if (!autosensData.absorbing && autosensData.cob < 0.5) { + autosensData.mealCarbs = 0.0 + } + // check previous "type" value, and if it wasn't csf, set a mealAbsorption start flag + if (autosensData.type != "csf") { +// process.stderr.write("("); + autosensData.mealStartCounter = 0 + } + autosensData.mealStartCounter++ + autosensData.type = "csf" + } else { + // check previous "type" value, and if it was csf, set a mealAbsorption end flag + val currentBasal = profile.getBasal(bgTime) + // always exclude the first 45m after each carb entry + //if (iob.iob > currentBasal || uam ) { + if (iob.iob > 2 * currentBasal || autosensData.uam || autosensData.mealStartCounter < 9) { + autosensData.mealStartCounter++ + autosensData.uam = deviation > 0 + autosensData.type = "uam" + } else { + autosensData.type = "non-meal" + } + } + + // Exclude meal-related deviations (carb absorption) from autosens + when (autosensData.type) { + "non-meal" -> { + when { + abs(deviation) < Constants.DEVIATION_TO_BE_EQUAL -> { + autosensData.pastSensitivity += "=" + autosensData.validDeviation = true + } + + deviation > 0 -> { + autosensData.pastSensitivity += "+" + autosensData.validDeviation = true + } + + else -> { + autosensData.pastSensitivity += "-" + autosensData.validDeviation = true + } + } + } + + "uam" -> { + autosensData.pastSensitivity += "u" + } + + else -> { + autosensData.pastSensitivity += "x" + } + } + + // add an extra negative deviation if a high temptarget is running and exercise mode is set + // TODO AS-FIX + @Suppress("SimplifyBooleanWithConstants") + if (false && sp.getBoolean(R.string.key_high_temptarget_raises_sensitivity, SMBDefaults.high_temptarget_raises_sensitivity)) { + val tempTarget = treatmentsPlugin.getTempTargetFromHistory(bgTime) + if (tempTarget != null && tempTarget.target() >= 100) { + autosensData.extraDeviation.add(-(tempTarget.target() - 100) / 20) + } + } + + // add one neutral deviation every 2 hours to help decay over long exclusion periods + val calendar = GregorianCalendar() + calendar.timeInMillis = bgTime + val min = calendar[Calendar.MINUTE] + val hours = calendar[Calendar.HOUR_OF_DAY] + if (min in 0..4 && hours % 2 == 0) autosensData.extraDeviation.add(0.0) + previous = autosensData + if (bgTime < DateUtil.now()) autosensDataTable.put(bgTime, autosensData) + aapsLogger.debug(LTag.AUTOSENS, "Running detectSensitivity from: " + dateUtil.dateAndTimeString(oldestTimeWithData) + " to: " + dateUtil.dateAndTimeString(bgTime) + " lastDataTime:" + iobCobCalculatorPlugin.lastDataTime()) + val sensitivity = iobCobCalculatorPlugin.detectSensitivityWithLock(oldestTimeWithData, bgTime) + aapsLogger.debug(LTag.AUTOSENS, "Sensitivity result: $sensitivity") + autosensData.autosensResult = sensitivity + aapsLogger.debug(LTag.AUTOSENS, autosensData.toString()) + } + } + Thread { + SystemClock.sleep(1000) + rxBus.send(EventAutosensCalculationFinished(cause)) + }.start() + } finally { + mWakeLock?.release() + rxBus.send(EventIobCalculationProgress("")) + aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: $from") + aapsLogger.debug(LTag.AUTOSENS, "Midnights: " + MidnightTime.log()) + profiler.log(LTag.AUTOSENS, "IobCobOref1Thread", start) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.java b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.java deleted file mode 100644 index 0c7a9444d0..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.java +++ /dev/null @@ -1,330 +0,0 @@ -package info.nightscout.androidaps.plugins.iob.iobCobCalculator; - -import android.content.Context; -import android.os.PowerManager; -import android.os.SystemClock; - -import androidx.collection.LongSparseArray; - -import java.util.ArrayList; -import java.util.List; - -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.Profile; -import info.nightscout.androidaps.db.Treatment; -import info.nightscout.androidaps.events.Event; -import info.nightscout.androidaps.interfaces.PluginType; -import info.nightscout.androidaps.interfaces.ProfileFunction; -import info.nightscout.androidaps.logging.AAPSLogger; -import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults; -import info.nightscout.androidaps.plugins.bus.RxBusWrapper; -import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; -import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensBgLoaded; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress; -import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin; -import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin; -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.DecimalFormatter; -import info.nightscout.androidaps.utils.FabricPrivacy; -import info.nightscout.androidaps.utils.MidnightTime; -import info.nightscout.androidaps.utils.Profiler; -import info.nightscout.androidaps.utils.T; -import info.nightscout.androidaps.utils.buildHelper.BuildHelper; -import info.nightscout.androidaps.utils.resources.ResourceHelper; -import info.nightscout.androidaps.utils.sharedPreferences.SP; - -import static info.nightscout.androidaps.utils.DateUtil.now; - -/** - * Created by mike on 23.01.2018. - */ - -public class IobCobThread extends Thread { - private final Event cause; - - @Inject AAPSLogger aapsLogger; - @Inject SP sp; - @Inject RxBusWrapper rxBus; - @Inject ResourceHelper resourceHelper; - @Inject ProfileFunction profileFunction; - @Inject Context context; - @Inject SensitivityAAPSPlugin sensitivityAAPSPlugin; - @Inject SensitivityWeightedAveragePlugin sensitivityWeightedAveragePlugin; - @Inject BuildHelper buildHelper; - @Inject Profiler profiler; - @Inject FabricPrivacy fabricPrivacy; - @Inject DateUtil dateUtil; - - private final HasAndroidInjector injector; - private final IobCobCalculatorPlugin iobCobCalculatorPlugin; // cannot be injected : HistoryBrowser uses different instance - private final TreatmentsPlugin treatmentsPlugin; // cannot be injected : HistoryBrowser uses different instance - private final boolean bgDataReload; - private final boolean limitDataToOldestAvailable; - private final String from; - private final long end; - - private PowerManager.WakeLock mWakeLock; - - @Inject IobCobThread(HasAndroidInjector injector, IobCobCalculatorPlugin iobCobCalculatorPlugin, TreatmentsPlugin treatmentsPlugin, String from, long end, boolean bgDataReload, boolean limitDataToOldestAvailable, Event cause) { - super(); - injector.androidInjector().inject(this); - this.injector = injector; - this.iobCobCalculatorPlugin = iobCobCalculatorPlugin; - this.treatmentsPlugin = treatmentsPlugin; - - this.bgDataReload = bgDataReload; - this.limitDataToOldestAvailable = limitDataToOldestAvailable; - this.from = from; - this.cause = cause; - this.end = end; - - PowerManager powerManager = (PowerManager) context.getApplicationContext().getSystemService(Context.POWER_SERVICE); - if (powerManager != null) - mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, resourceHelper.gs(R.string.app_name) + ":iobCobThread"); - } - - @Override - public final void run() { - long start = DateUtil.now(); - if (mWakeLock != null) - mWakeLock.acquire(T.mins(10).msecs()); - try { - aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread started: " + from); - if (!profileFunction.isProfileValid("IobCobThread")) { - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No profile): " + from); - return; // app still initializing - } - //log.debug("Locking calculateSensitivityData"); - - long oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable); - - synchronized (iobCobCalculatorPlugin.getDataLock()) { - if (bgDataReload) { - iobCobCalculatorPlugin.loadBgData(end); - iobCobCalculatorPlugin.createBucketedData(); - rxBus.send(new EventAutosensBgLoaded(cause)); - } - List bucketed_data = iobCobCalculatorPlugin.getBucketedData(); - LongSparseArray autosensDataTable = iobCobCalculatorPlugin.getAutosensDataTable(); - - if (bucketed_data == null || bucketed_data.size() < 3) { - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No bucketed data available): " + from); - return; - } - - long prevDataTime = IobCobCalculatorPlugin.roundUpTime(bucketed_data.get(bucketed_data.size() - 3).getTimestamp()); - aapsLogger.debug(LTag.AUTOSENS, "Prev data time: " + dateUtil.dateAndTimeString(prevDataTime)); - AutosensData previous = autosensDataTable.get(prevDataTime); - // start from oldest to be able sub cob - for (int i = bucketed_data.size() - 4; i >= 0; i--) { - String progress = i + (buildHelper.isDev() ? " (" + from + ")" : ""); - rxBus.send(new EventIobCalculationProgress(progress)); - - if (iobCobCalculatorPlugin.stopCalculationTrigger) { - iobCobCalculatorPlugin.stopCalculationTrigger = false; - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): " + from); - return; - } - // check if data already exists - long bgTime = bucketed_data.get(i).getTimestamp(); - bgTime = IobCobCalculatorPlugin.roundUpTime(bgTime); - if (bgTime > IobCobCalculatorPlugin.roundUpTime(now())) - continue; - - AutosensData existing; - if ((existing = autosensDataTable.get(bgTime)) != null) { - previous = existing; - continue; - } - - Profile profile = profileFunction.getProfile(bgTime); - if (profile == null) { - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (no profile): " + from); - return; // profile not set yet - } - - aapsLogger.debug(LTag.AUTOSENS, "Processing calculation thread: " + from + " (" + i + "/" + bucketed_data.size() + ")"); - - double sens = profile.getIsfMgdl(bgTime); - - AutosensData autosensData = new AutosensData(injector); - autosensData.time = bgTime; - if (previous != null) - autosensData.activeCarbsList = previous.cloneCarbsList(); - else - autosensData.activeCarbsList = new ArrayList<>(); - - //console.error(bgTime , bucketed_data[i].glucose); - double bg; - double avgDelta; - double delta; - bg = bucketed_data.get(i).getValue(); - if (bg < 39 || bucketed_data.get(i + 3).getValue() < 39) { - aapsLogger.error("! value < 39"); - continue; - } - autosensData.bg = bg; - delta = (bg - bucketed_data.get(i + 1).getValue()); - avgDelta = (bg - bucketed_data.get(i + 3).getValue()) / 3; - - IobTotal iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTemps(bgTime, profile); - - double bgi = -iob.activity * sens * 5; - double deviation = delta - bgi; - double avgDeviation = Math.round((avgDelta - bgi) * 1000) / 1000d; - - double slopeFromMaxDeviation = 0; - double slopeFromMinDeviation = 999; - double maxDeviation = 0; - double minDeviation = 999; - - // https://github.com/openaps/oref0/blob/master/lib/determine-basal/cob-autosens.js#L169 - if (i < bucketed_data.size() - 16) { // we need 1h of data to calculate minDeviationSlope - long hourago = bgTime + 10 * 1000 - 60 * 60 * 1000L; - AutosensData hourAgoData = iobCobCalculatorPlugin.getAutosensData(hourago); - if (hourAgoData != null) { - int initialIndex = autosensDataTable.indexOfKey(hourAgoData.time); - aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketed_data.size() + " i=" + i + " hourAgoData=" + hourAgoData.toString()); - int past = 1; - try { - for (; past < 12; past++) { - AutosensData ad = autosensDataTable.valueAt(initialIndex + past); - aapsLogger.debug(LTag.AUTOSENS, ">>>>> past=" + past + " ad=" + (ad != null ? ad.toString() : null)); - if (ad == null) { - aapsLogger.debug(LTag.AUTOSENS, autosensDataTable.toString()); - aapsLogger.debug(LTag.AUTOSENS, bucketed_data.toString()); - aapsLogger.debug(LTag.AUTOSENS, iobCobCalculatorPlugin.getBgReadings().toString()); - Notification notification = new Notification(Notification.SENDLOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW); - rxBus.send(new EventNewNotification(notification)); - sp.putBoolean("log_AUTOSENS", true); - break; - } - // let it here crash on NPE to get more data as i cannot reproduce this bug - double deviationSlope = (ad.avgDeviation - avgDeviation) / (ad.time - bgTime) * 1000 * 60 * 5; - if (ad.avgDeviation > maxDeviation) { - slopeFromMaxDeviation = Math.min(0, deviationSlope); - maxDeviation = ad.avgDeviation; - } - if (ad.avgDeviation < minDeviation) { - slopeFromMinDeviation = Math.max(0, deviationSlope); - minDeviation = ad.avgDeviation; - } - - //if (Config.isEnabled(L.AUTOSENS)) - // log.debug("Deviations: " + new Date(bgTime) + new Date(ad.time) + " avgDeviation=" + avgDeviation + " deviationSlope=" + deviationSlope + " slopeFromMaxDeviation=" + slopeFromMaxDeviation + " slopeFromMinDeviation=" + slopeFromMinDeviation); - } - } catch (Exception e) { - aapsLogger.error("Unhandled exception", e); - fabricPrivacy.logException(e); - aapsLogger.debug(autosensDataTable.toString()); - aapsLogger.debug(bucketed_data.toString()); - aapsLogger.debug(iobCobCalculatorPlugin.getBgReadings().toString()); - Notification notification = new Notification(Notification.SENDLOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW); - rxBus.send(new EventNewNotification(notification)); - sp.putBoolean("log_AUTOSENS", true); - break; - } - } else { - aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketed_data.size() + " i=" + i + " hourAgoData=" + "null"); - } - } - - List recentCarbTreatments = treatmentsPlugin.getCarbTreatments5MinBackFromHistory(bgTime); - for (Treatment recentCarbTreatment : recentCarbTreatments) { - autosensData.carbsFromBolus += recentCarbTreatment.carbs; - boolean isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled(); - autosensData.activeCarbsList.add(autosensData.new CarbsInPast(recentCarbTreatment, isAAPSOrWeighted)); - autosensData.pastSensitivity += "[" + DecimalFormatter.to0Decimal(recentCarbTreatment.carbs) + "g]"; - } - - - // if we are absorbing carbs - if (previous != null && previous.cob > 0) { - // calculate sum of min carb impact from all active treatments - double totalMinCarbsImpact = 0d; - if (sensitivityAAPSPlugin.isEnabled(PluginType.SENSITIVITY) || sensitivityWeightedAveragePlugin.isEnabled(PluginType.SENSITIVITY)) { - //when the impact depends on a max time, sum them up as smaller carb sizes make them smaller - for (int ii = 0; ii < autosensData.activeCarbsList.size(); ++ii) { - AutosensData.CarbsInPast c = autosensData.activeCarbsList.get(ii); - totalMinCarbsImpact += c.min5minCarbImpact; - } - } else { - //Oref sensitivity - totalMinCarbsImpact = sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact); - } - - // figure out how many carbs that represents - // but always assume at least 3mg/dL/5m (default) absorption per active treatment - double ci = Math.max(deviation, totalMinCarbsImpact); - if (ci != deviation) - autosensData.failoverToMinAbsorbtionRate = true; - autosensData.absorbed = ci * profile.getIc(bgTime) / sens; - // and add that to the running total carbsAbsorbed - autosensData.cob = Math.max(previous.cob - autosensData.absorbed, 0d); - autosensData.substractAbosorbedCarbs(); - autosensData.usedMinCarbsImpact = totalMinCarbsImpact; - } - boolean isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled(); - autosensData.removeOldCarbs(bgTime, isAAPSOrWeighted); - autosensData.cob += autosensData.carbsFromBolus; - autosensData.deviation = deviation; - autosensData.bgi = bgi; - autosensData.delta = delta; - autosensData.avgDelta = avgDelta; - autosensData.avgDeviation = avgDeviation; - autosensData.slopeFromMaxDeviation = slopeFromMaxDeviation; - autosensData.slopeFromMinDeviation = slopeFromMinDeviation; - - - // calculate autosens only without COB - if (autosensData.cob <= 0) { - if (Math.abs(deviation) < Constants.DEVIATION_TO_BE_EQUAL) { - autosensData.pastSensitivity += "="; - autosensData.validDeviation = true; - } else if (deviation > 0) { - autosensData.pastSensitivity += "+"; - autosensData.validDeviation = true; - } else { - autosensData.pastSensitivity += "-"; - autosensData.validDeviation = true; - } - } else { - autosensData.pastSensitivity += "C"; - } - //log.debug("TIME: " + new Date(bgTime).toString() + " BG: " + bg + " SENS: " + sens + " DELTA: " + delta + " AVGDELTA: " + avgDelta + " IOB: " + iob.iob + " ACTIVITY: " + iob.activity + " BGI: " + bgi + " DEVIATION: " + deviation); - - previous = autosensData; - if (bgTime < now()) - autosensDataTable.put(bgTime, autosensData); - aapsLogger.debug(LTag.AUTOSENS, "Running detectSensitivity from: " + dateUtil.dateAndTimeString(oldestTimeWithData) + " to: " + dateUtil.dateAndTimeString(bgTime) + " lastDataTime:" + iobCobCalculatorPlugin.lastDataTime()); - AutosensResult sensitivity = iobCobCalculatorPlugin.detectSensitivityWithLock(oldestTimeWithData, bgTime); - aapsLogger.debug(LTag.AUTOSENS, "Sensitivity result: " + sensitivity.toString()); - autosensData.autosensResult = sensitivity; - aapsLogger.debug(LTag.AUTOSENS, autosensData.toString()); - } - } - new Thread(() -> { - SystemClock.sleep(1000); - rxBus.send(new EventAutosensCalculationFinished(cause)); - }).start(); - } finally { - if (mWakeLock != null) - mWakeLock.release(); - rxBus.send(new EventIobCalculationProgress("")); - aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: " + from); - aapsLogger.debug(LTag.AUTOSENS, "Midnights: " + MidnightTime.log()); - profiler.log(LTag.AUTOSENS, "IobCobThread", start); - } - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.kt new file mode 100644 index 0000000000..1f67032978 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.kt @@ -0,0 +1,277 @@ +package info.nightscout.androidaps.plugins.iob.iobCobCalculator + +import android.content.Context +import android.os.PowerManager +import android.os.SystemClock +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.Constants +import info.nightscout.androidaps.R +import info.nightscout.androidaps.events.Event +import info.nightscout.androidaps.interfaces.PluginType +import info.nightscout.androidaps.interfaces.ProfileFunction +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults +import info.nightscout.androidaps.plugins.bus.RxBusWrapper +import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification +import info.nightscout.androidaps.plugins.general.overview.notifications.Notification +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin.Companion.roundUpTime +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensBgLoaded +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress +import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin +import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.utils.* +import info.nightscout.androidaps.utils.buildHelper.BuildHelper +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.sharedPreferences.SP +import java.util.* +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min +import kotlin.math.roundToLong + +class IobCobThread @Inject internal constructor( + private val injector: HasAndroidInjector, + private val iobCobCalculatorPlugin: IobCobCalculatorPlugin, // cannot be injected : HistoryBrowser uses different instance + private val treatmentsPlugin: TreatmentsPlugin, // cannot be injected : HistoryBrowser uses different instance + private val from: String, + private val end: Long, + private val bgDataReload: Boolean, + private val limitDataToOldestAvailable: Boolean, + private val cause: Event +) : Thread() { + + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var sp: SP + @Inject lateinit var rxBus: RxBusWrapper + @Inject lateinit var resourceHelper: ResourceHelper + @Inject lateinit var profileFunction: ProfileFunction + @Inject lateinit var context: Context + @Inject lateinit var sensitivityAAPSPlugin: SensitivityAAPSPlugin + @Inject lateinit var sensitivityWeightedAveragePlugin: SensitivityWeightedAveragePlugin + @Inject lateinit var buildHelper: BuildHelper + @Inject lateinit var profiler: Profiler + @Inject lateinit var fabricPrivacy: FabricPrivacy + @Inject lateinit var dateUtil: DateUtil + + private var mWakeLock: PowerManager.WakeLock? = null + + init { + injector.androidInjector().inject(this) + mWakeLock = (context.applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, resourceHelper.gs(R.string.app_name) + ":iobCobThread") + } + + override fun run() { + val start = DateUtil.now() + mWakeLock?.acquire(T.mins(10).msecs()) + try { + aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread started: $from") + if (!profileFunction.isProfileValid("IobCobThread")) { + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No profile): $from") + return // app still initializing + } + //log.debug("Locking calculateSensitivityData"); + val oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable) + synchronized(iobCobCalculatorPlugin.dataLock) { + if (bgDataReload) { + iobCobCalculatorPlugin.loadBgData(end) + iobCobCalculatorPlugin.createBucketedData() + rxBus.send(EventAutosensBgLoaded(cause)) + } + val bucketedData = iobCobCalculatorPlugin.bucketedData + val autosensDataTable = iobCobCalculatorPlugin.autosensDataTable + if (bucketedData == null || bucketedData.size < 3) { + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No bucketed data available): $from") + return + } + val prevDataTime = roundUpTime(bucketedData[bucketedData.size - 3].timestamp) + aapsLogger.debug(LTag.AUTOSENS, "Prev data time: " + dateUtil.dateAndTimeString(prevDataTime)) + var previous = autosensDataTable[prevDataTime] + // start from oldest to be able sub cob + for (i in bucketedData.size - 4 downTo 0) { + val progress = i.toString() + if (buildHelper.isDev()) " ($from)" else "" + rxBus.send(EventIobCalculationProgress(progress)) + if (iobCobCalculatorPlugin.stopCalculationTrigger) { + iobCobCalculatorPlugin.stopCalculationTrigger = false + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): $from") + return + } + // check if data already exists + var bgTime = bucketedData[i].timestamp + bgTime = roundUpTime(bgTime) + if (bgTime > roundUpTime(DateUtil.now())) continue + var existing: AutosensData? + if (autosensDataTable[bgTime].also { existing = it } != null) { + previous = existing + continue + } + val profile = profileFunction.getProfile(bgTime) + if (profile == null) { + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (no profile): $from") + return // profile not set yet + } + aapsLogger.debug(LTag.AUTOSENS, "Processing calculation thread: " + from + " (" + i + "/" + bucketedData.size + ")") + val sens = profile.getIsfMgdl(bgTime) + val autosensData = AutosensData(injector) + autosensData.time = bgTime + if (previous != null) autosensData.activeCarbsList = previous.cloneCarbsList() else autosensData.activeCarbsList = ArrayList() + + //console.error(bgTime , bucketed_data[i].glucose); + var avgDelta: Double + var delta: Double + val bg: Double = bucketedData[i].value + if (bg < 39 || bucketedData[i + 3].value < 39) { + aapsLogger.error("! value < 39") + continue + } + autosensData.bg = bg + delta = bg - bucketedData[i + 1].value + avgDelta = (bg - bucketedData[i + 3].value) / 3 + val iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTemps(bgTime, profile) + val bgi = -iob.activity * sens * 5 + val deviation = delta - bgi + val avgDeviation = ((avgDelta - bgi) * 1000).roundToLong() / 1000.0 + var slopeFromMaxDeviation = 0.0 + var slopeFromMinDeviation = 999.0 + + // https://github.com/openaps/oref0/blob/master/lib/determine-basal/cob-autosens.js#L169 + if (i < bucketedData.size - 16) { // we need 1h of data to calculate minDeviationSlope + @Suppress("UNUSED_VARIABLE") var maxDeviation = 0.0 + @Suppress("UNUSED_VARIABLE") var minDeviation = 999.0 + val hourAgo = bgTime + 10 * 1000 - 60 * 60 * 1000L + val hourAgoData = iobCobCalculatorPlugin.getAutosensData(hourAgo) + if (hourAgoData != null) { + val initialIndex = autosensDataTable.indexOfKey(hourAgoData.time) + aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketedData.size + " i=" + i + " hourAgoData=" + hourAgoData.toString()) + var past = 1 + try { + while (past < 12) { + val ad = autosensDataTable.valueAt(initialIndex + past) + aapsLogger.debug(LTag.AUTOSENS, ">>>>> past=" + past + " ad=" + ad?.toString()) + if (ad == null) { + aapsLogger.debug(LTag.AUTOSENS, autosensDataTable.toString()) + aapsLogger.debug(LTag.AUTOSENS, bucketedData.toString()) + aapsLogger.debug(LTag.AUTOSENS, iobCobCalculatorPlugin.bgReadings.toString()) + val notification = Notification(Notification.SENDLOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW) + rxBus.send(EventNewNotification(notification)) + sp.putBoolean("log_AUTOSENS", true) + break + } + // let it here crash on NPE to get more data as i cannot reproduce this bug + val deviationSlope = (ad.avgDeviation - avgDeviation) / (ad.time - bgTime) * 1000 * 60 * 5 + if (ad.avgDeviation > maxDeviation) { + slopeFromMaxDeviation = min(0.0, deviationSlope) + maxDeviation = ad.avgDeviation + } + if (ad.avgDeviation < minDeviation) { + slopeFromMinDeviation = max(0.0, deviationSlope) + minDeviation = ad.avgDeviation + } + past++ + } + } catch (e: Exception) { + aapsLogger.error("Unhandled exception", e) + fabricPrivacy.logException(e) + aapsLogger.debug(autosensDataTable.toString()) + aapsLogger.debug(bucketedData.toString()) + aapsLogger.debug(iobCobCalculatorPlugin.bgReadings.toString()) + val notification = Notification(Notification.SENDLOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW) + rxBus.send(EventNewNotification(notification)) + sp.putBoolean("log_AUTOSENS", true) + break + } + } else { + aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketedData.size + " i=" + i + " hourAgoData=" + "null") + } + } + val recentCarbTreatments = treatmentsPlugin.getCarbTreatments5MinBackFromHistory(bgTime) + for (recentCarbTreatment in recentCarbTreatments) { + autosensData.carbsFromBolus += recentCarbTreatment.carbs + val isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled() + autosensData.activeCarbsList.add(autosensData.CarbsInPast(recentCarbTreatment, isAAPSOrWeighted)) + autosensData.pastSensitivity += "[" + DecimalFormatter.to0Decimal(recentCarbTreatment.carbs) + "g]" + } + + // if we are absorbing carbs + if (previous != null && previous.cob > 0) { + // calculate sum of min carb impact from all active treatments + var totalMinCarbsImpact = 0.0 + if (sensitivityAAPSPlugin.isEnabled(PluginType.SENSITIVITY) || sensitivityWeightedAveragePlugin.isEnabled(PluginType.SENSITIVITY)) { + //when the impact depends on a max time, sum them up as smaller carb sizes make them smaller + for (ii in autosensData.activeCarbsList.indices) { + val c = autosensData.activeCarbsList[ii] + totalMinCarbsImpact += c.min5minCarbImpact + } + } else { + //Oref sensitivity + totalMinCarbsImpact = sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact) + } + + // figure out how many carbs that represents + // but always assume at least 3mg/dL/5m (default) absorption per active treatment + val ci = max(deviation, totalMinCarbsImpact) + if (ci != deviation) autosensData.failoverToMinAbsorbtionRate = true + autosensData.absorbed = ci * profile.getIc(bgTime) / sens + // and add that to the running total carbsAbsorbed + autosensData.cob = max(previous.cob - autosensData.absorbed, 0.0) + autosensData.substractAbosorbedCarbs() + autosensData.usedMinCarbsImpact = totalMinCarbsImpact + } + val isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled() + autosensData.removeOldCarbs(bgTime, isAAPSOrWeighted) + autosensData.cob += autosensData.carbsFromBolus + autosensData.deviation = deviation + autosensData.bgi = bgi + autosensData.delta = delta + autosensData.avgDelta = avgDelta + autosensData.avgDeviation = avgDeviation + autosensData.slopeFromMaxDeviation = slopeFromMaxDeviation + autosensData.slopeFromMinDeviation = slopeFromMinDeviation + + // calculate autosens only without COB + if (autosensData.cob <= 0) { + when { + abs(deviation) < Constants.DEVIATION_TO_BE_EQUAL -> { + autosensData.pastSensitivity += "=" + autosensData.validDeviation = true + } + + deviation > 0 -> { + autosensData.pastSensitivity += "+" + autosensData.validDeviation = true + } + + else -> { + autosensData.pastSensitivity += "-" + autosensData.validDeviation = true + } + } + } else { + autosensData.pastSensitivity += "C" + } + previous = autosensData + if (bgTime < DateUtil.now()) autosensDataTable.put(bgTime, autosensData) + aapsLogger.debug(LTag.AUTOSENS, "Running detectSensitivity from: " + dateUtil.dateAndTimeString(oldestTimeWithData) + " to: " + dateUtil.dateAndTimeString(bgTime) + " lastDataTime:" + iobCobCalculatorPlugin.lastDataTime()) + val sensitivity = iobCobCalculatorPlugin.detectSensitivityWithLock(oldestTimeWithData, bgTime) + aapsLogger.debug(LTag.AUTOSENS, "Sensitivity result: $sensitivity") + autosensData.autosensResult = sensitivity + aapsLogger.debug(LTag.AUTOSENS, autosensData.toString()) + } + } + Thread { + SystemClock.sleep(1000) + rxBus.send(EventAutosensCalculationFinished(cause)) + }.start() + } finally { + mWakeLock?.release() + rxBus.send(EventIobCalculationProgress("")) + aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: $from") + aapsLogger.debug(LTag.AUTOSENS, "Midnights: " + MidnightTime.log()) + profiler.log(LTag.AUTOSENS, "IobCobThread", start) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.java index 500518a419..55e61bb5c6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.java @@ -24,13 +24,12 @@ import info.nightscout.androidaps.db.ProfileSwitch; import info.nightscout.androidaps.interfaces.IobCobCalculatorInterface; import info.nightscout.androidaps.interfaces.PluginDescription; import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.interfaces.ProfileFunction; import info.nightscout.androidaps.logging.AAPSLogger; import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.interfaces.ProfileFunction; -import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData; import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.resources.ResourceHelper; import info.nightscout.androidaps.utils.sharedPreferences.SP; @@ -161,7 +160,7 @@ public class SensitivityAAPSPlugin extends AbstractSensitivityPlugin { Arrays.sort(deviations); - double percentile = IobCobCalculatorPlugin.percentile(deviations, 0.50); + double percentile = IobCobCalculatorPlugin.Companion.percentile(deviations, 0.50); double basalOff = percentile * (60.0 / 5.0) / sens; double ratio = 1 + (basalOff / profile.getMaxDailyBasal()); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.java index 233cd675d0..fbc1baa5e1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.java @@ -207,8 +207,8 @@ public class SensitivityOref1Plugin extends AbstractSensitivityPlugin { getAapsLogger().debug(LTag.AUTOSENS, "Records: " + index + " " + pastSensitivity); Arrays.sort(deviations); - double pSensitive = IobCobCalculatorPlugin.percentile(deviations, 0.50); - double pResistant = IobCobCalculatorPlugin.percentile(deviations, 0.50); + double pSensitive = IobCobCalculatorPlugin.Companion.percentile(deviations, 0.50); + double pResistant = IobCobCalculatorPlugin.Companion.percentile(deviations, 0.50); double basalOff = 0; diff --git a/app/src/main/java/info/nightscout/androidaps/utils/buildHelper/BuildHelper.kt b/app/src/main/java/info/nightscout/androidaps/utils/buildHelper/BuildHelper.kt index ff11dfb096..2eafc12ebb 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/buildHelper/BuildHelper.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/buildHelper/BuildHelper.kt @@ -8,13 +8,13 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class BuildHelper @Inject constructor(private val config: Config) { +class BuildHelper @Inject constructor(private val config: Config, loggerUtils: LoggerUtils) { private var devBranch = false private var engineeringMode = false init { - val extFilesDir = LoggerUtils.getLogDirectory() + val extFilesDir = loggerUtils.logDirectory val engineeringModeSemaphore = File(extFilesDir, "engineering__mode") engineeringMode = engineeringModeSemaphore.exists() && engineeringModeSemaphore.isFile diff --git a/app/src/main/java/info/nightscout/androidaps/utils/wizard/BolusWizard.kt b/app/src/main/java/info/nightscout/androidaps/utils/wizard/BolusWizard.kt index 5f09d04d0c..e690ce4dba 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/wizard/BolusWizard.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/wizard/BolusWizard.kt @@ -194,7 +194,7 @@ class BolusWizard @Inject constructor( glucoseStatus = GlucoseStatus(injector).glucoseStatusData glucoseStatus?.let { if (useTrend) { - trend = it.short_avgdelta + trend = it.shortAvgDelta insulinFromTrend = Profile.fromMgdlToUnits(trend, profileFunction.getUnits()) * 3 / sens } } diff --git a/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizardEntry.kt b/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizardEntry.kt index abfe8b4bd9..39da06c1f5 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizardEntry.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizardEntry.kt @@ -112,9 +112,9 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec var trend = false if (useTrend() == YES) { trend = true - } else if (useTrend() == POSITIVE_ONLY && glucoseStatus != null && glucoseStatus.short_avgdelta > 0) { + } else if (useTrend() == POSITIVE_ONLY && glucoseStatus != null && glucoseStatus.shortAvgDelta > 0) { trend = true - } else if (useTrend() == NEGATIVE_ONLY && glucoseStatus != null && glucoseStatus.short_avgdelta < 0) { + } else if (useTrend() == NEGATIVE_ONLY && glucoseStatus != null && glucoseStatus.shortAvgDelta < 0) { trend = true } val percentage = sp.getDouble(R.string.key_boluswizard_percentage, 100.0) diff --git a/app/src/test/java/info/nightscout/androidaps/data/QuickWizardTest.kt b/app/src/test/java/info/nightscout/androidaps/data/QuickWizardTest.kt index 6504df440f..6deef1f826 100644 --- a/app/src/test/java/info/nightscout/androidaps/data/QuickWizardTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/data/QuickWizardTest.kt @@ -23,7 +23,7 @@ import org.powermock.core.classloader.annotations.PrepareForTest import org.powermock.modules.junit4.PowerMockRunner @RunWith(PowerMockRunner::class) -@PrepareForTest(Profile::class) +@PrepareForTest(Profile::class, IobCobCalculatorPlugin::class) class QuickWizardTest : TestBase() { @Mock lateinit var sp: SP @@ -68,7 +68,7 @@ class QuickWizardTest : TestBase() { @Test fun test() { quickWizard.setData(array) - Assert.assertEquals("Lunch", quickWizard.get(1).buttonText()) + Assert.assertEquals("Lunch", quickWizard[1].buttonText()) } @Test fun active() { diff --git a/app/src/test/java/info/nightscout/androidaps/interfaces/ConstraintsCheckerTest.kt b/app/src/test/java/info/nightscout/androidaps/interfaces/ConstraintsCheckerTest.kt index ac09a3a037..0fc6acac16 100644 --- a/app/src/test/java/info/nightscout/androidaps/interfaces/ConstraintsCheckerTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/interfaces/ConstraintsCheckerTest.kt @@ -18,6 +18,7 @@ import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin import info.nightscout.androidaps.plugins.constraints.objectives.objectives.Objective import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin +import info.nightscout.androidaps.plugins.general.maintenance.LoggerUtils import info.nightscout.androidaps.plugins.general.nsclient.NSUpload import info.nightscout.androidaps.plugins.general.nsclient.UploadQueue import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin @@ -47,7 +48,11 @@ import java.util.* * Created by mike on 18.03.2018. */ @RunWith(PowerMockRunner::class) -@PrepareForTest(MainApp::class, ConfigBuilderPlugin::class, ConstraintChecker::class, SP::class, Context::class, OpenAPSAMAPlugin::class, OpenAPSSMBPlugin::class, TreatmentsPlugin::class, TreatmentService::class, VirtualPumpPlugin::class, DetailedBolusInfoStorage::class, GlimpPlugin::class, Profiler::class, UserEntryLogger::class) +@PrepareForTest( + MainApp::class, ConfigBuilderPlugin::class, ConstraintChecker::class, SP::class, Context::class, + OpenAPSAMAPlugin::class, OpenAPSSMBPlugin::class, TreatmentsPlugin::class, TreatmentService::class, + VirtualPumpPlugin::class, DetailedBolusInfoStorage::class, GlimpPlugin::class, Profiler::class, + UserEntryLogger::class, IobCobCalculatorPlugin::class, LoggerUtils::class) class ConstraintsCheckerTest : TestBaseWithProfile() { @Mock lateinit var activePlugin: ActivePluginProvider @@ -63,8 +68,8 @@ class ConstraintsCheckerTest : TestBaseWithProfile() { @Mock lateinit var nsUpload: NSUpload @Mock lateinit var uploadQueue: UploadQueue @Mock lateinit var uel: UserEntryLogger + @Mock lateinit var loggerUtils: LoggerUtils - private var buildHelper = BuildHelper(Config()) lateinit var danaPump: DanaPump lateinit var constraintChecker: ConstraintChecker @@ -124,7 +129,7 @@ class ConstraintsCheckerTest : TestBaseWithProfile() { insightPlugin = LocalInsightPlugin(injector, aapsLogger, rxBus, resourceHelper, treatmentsPlugin, sp, commandQueue, profileFunction, nsUpload, context, uploadQueue, Config(), dateUtil) openAPSSMBPlugin = OpenAPSSMBPlugin(injector, aapsLogger, rxBus, constraintChecker, resourceHelper, profileFunction, context, activePlugin, treatmentsPlugin, iobCobCalculatorPlugin, hardLimits, profiler, sp) openAPSAMAPlugin = OpenAPSAMAPlugin(injector, aapsLogger, rxBus, constraintChecker, resourceHelper, profileFunction, context, activePlugin, treatmentsPlugin, iobCobCalculatorPlugin, hardLimits, profiler, fabricPrivacy) - safetyPlugin = SafetyPlugin(injector, aapsLogger, resourceHelper, sp, rxBus, constraintChecker, openAPSAMAPlugin, openAPSSMBPlugin, sensitivityOref1Plugin, activePlugin, hardLimits, buildHelper, treatmentsPlugin, Config()) + safetyPlugin = SafetyPlugin(injector, aapsLogger, resourceHelper, sp, rxBus, constraintChecker, openAPSAMAPlugin, openAPSSMBPlugin, sensitivityOref1Plugin, activePlugin, hardLimits, BuildHelper(Config(), loggerUtils), treatmentsPlugin, Config()) val constraintsPluginsList = ArrayList() constraintsPluginsList.add(safetyPlugin) constraintsPluginsList.add(objectivesPlugin) diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/aps/loop/LoopPluginTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/aps/loop/LoopPluginTest.kt index 3d752b3725..79c8c604e9 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/aps/loop/LoopPluginTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/aps/loop/LoopPluginTest.kt @@ -33,7 +33,9 @@ import org.powermock.core.classloader.annotations.PrepareForTest import org.powermock.modules.junit4.PowerMockRunner @RunWith(PowerMockRunner::class) -@PrepareForTest(ConstraintChecker::class, VirtualPumpPlugin::class, FabricPrivacy::class, ReceiverStatusStore::class) +@PrepareForTest( + ConstraintChecker::class, VirtualPumpPlugin::class, FabricPrivacy::class, ReceiverStatusStore::class, + IobCobCalculatorPlugin::class) class LoopPluginTest : TestBase() { @Mock lateinit var sp: SP diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTestBase.kt b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTestBase.kt index df948f28c5..c669a78c1c 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTestBase.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTestBase.kt @@ -19,7 +19,7 @@ import org.junit.Before import org.mockito.Mock import org.powermock.core.classloader.annotations.PrepareForTest -@PrepareForTest(LastLocationDataContainer::class, AutomationPlugin::class) +@PrepareForTest(LastLocationDataContainer::class, AutomationPlugin::class, IobCobCalculatorPlugin::class) open class TriggerTestBase : TestBaseWithProfile() { @Mock lateinit var sp: SP diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePluginTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePluginTest.kt index 26706da190..385f327adb 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePluginTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePluginTest.kt @@ -13,12 +13,13 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.`when` import org.powermock.core.classloader.annotations.PrepareForTest import org.powermock.modules.junit4.PowerMockRunner import java.io.File @RunWith(PowerMockRunner::class) -@PrepareForTest(NSSettingsStatus::class, BuildHelper::class) +@PrepareForTest(NSSettingsStatus::class, BuildHelper::class, LoggerUtils::class) class MaintenancePluginTest : TestBase() { @Mock lateinit var injector: HasAndroidInjector @@ -27,28 +28,29 @@ class MaintenancePluginTest : TestBase() { @Mock lateinit var sp: SP @Mock lateinit var nsSettingsStatus: NSSettingsStatus @Mock lateinit var buildHelper: BuildHelper + @Mock lateinit var loggerUtils: LoggerUtils lateinit var sut: MaintenancePlugin @Before fun mock() { - sut = MaintenancePlugin(injector, context, resourceHelper, sp, nsSettingsStatus, aapsLogger, buildHelper, Config()) + sut = MaintenancePlugin(injector, context, resourceHelper, sp, nsSettingsStatus, aapsLogger, buildHelper, Config(), loggerUtils) + `when`(loggerUtils.suffix).thenReturn(".log.zip") + `when`(loggerUtils.logDirectory).thenReturn("src/test/res/logger") } - @Test fun logfilesTest() { - val logDirectory = "src/test/res/logger" - var logs = sut.getLogFiles(logDirectory, 2) + @Test fun logFilesTest() { + var logs = sut.getLogFiles(2) Assert.assertEquals(2, logs.size) Assert.assertEquals("AndroidAPS.log", logs[0].name) Assert.assertEquals("AndroidAPS.2018-01-03_01-01-00.1.zip", logs[1].name) - logs = sut.getLogFiles(logDirectory, 10) + logs = sut.getLogFiles(10) Assert.assertEquals(4, logs.size) } @Test fun zipLogsTest() { - val logDirectory = "src/test/res/logger" - val logs = sut.getLogFiles(logDirectory, 2) + val logs = sut.getLogFiles(2) val name = "AndroidAPS.log.zip" var zipFile = File("build/$name") zipFile = sut.zipLogs(zipFile, logs) diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatusTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatusTest.kt index 49230292e7..c61d97d6f8 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatusTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatusTest.kt @@ -52,9 +52,9 @@ class GlucoseStatusTest : TestBase() { val glucoseStatus = GlucoseStatus(injector).glucoseStatusData!! Assert.assertEquals(214.0, glucoseStatus.glucose, 0.001) Assert.assertEquals(-2.0, glucoseStatus.delta, 0.001) - Assert.assertEquals(-2.5, glucoseStatus.short_avgdelta, 0.001) // -2 -2.5 -3 deltas are relative to current value - Assert.assertEquals(-2.5, glucoseStatus.avgdelta, 0.001) // the same as short_avgdelta - Assert.assertEquals(-2.0, glucoseStatus.long_avgdelta, 0.001) // -2 -2 -2 -2 + Assert.assertEquals(-2.5, glucoseStatus.shortAvgDelta, 0.001) // -2 -2.5 -3 deltas are relative to current value + Assert.assertEquals(-2.5, glucoseStatus.avgDelta, 0.001) // the same as short_avgdelta + Assert.assertEquals(-2.0, glucoseStatus.longAvgDelta, 0.001) // -2 -2 -2 -2 Assert.assertEquals(1514766900000L, glucoseStatus.date) // latest date } @@ -63,9 +63,9 @@ class GlucoseStatusTest : TestBase() { val glucoseStatus: GlucoseStatus = GlucoseStatus(injector).glucoseStatusData!! Assert.assertEquals(215.0, glucoseStatus.glucose, 0.001) // (214+216) / 2 Assert.assertEquals(-1.0, glucoseStatus.delta, 0.001) - Assert.assertEquals(-1.0, glucoseStatus.short_avgdelta, 0.001) - Assert.assertEquals(-1.0, glucoseStatus.avgdelta, 0.001) - Assert.assertEquals(0.0, glucoseStatus.long_avgdelta, 0.001) + Assert.assertEquals(-1.0, glucoseStatus.shortAvgDelta, 0.001) + Assert.assertEquals(-1.0, glucoseStatus.avgDelta, 0.001) + Assert.assertEquals(0.0, glucoseStatus.longAvgDelta, 0.001) Assert.assertEquals(1514766900000L, glucoseStatus.date) // latest date, even when averaging } @@ -74,9 +74,9 @@ class GlucoseStatusTest : TestBase() { val glucoseStatus: GlucoseStatus = GlucoseStatus(injector).glucoseStatusData!! Assert.assertEquals(214.0, glucoseStatus.glucose, 0.001) Assert.assertEquals(0.0, glucoseStatus.delta, 0.001) - Assert.assertEquals(0.0, glucoseStatus.short_avgdelta, 0.001) // -2 -2.5 -3 deltas are relative to current value - Assert.assertEquals(0.0, glucoseStatus.avgdelta, 0.001) // the same as short_avgdelta - Assert.assertEquals(0.0, glucoseStatus.long_avgdelta, 0.001) // -2 -2 -2 -2 + Assert.assertEquals(0.0, glucoseStatus.shortAvgDelta, 0.001) // -2 -2.5 -3 deltas are relative to current value + Assert.assertEquals(0.0, glucoseStatus.avgDelta, 0.001) // the same as short_avgdelta + Assert.assertEquals(0.0, glucoseStatus.longAvgDelta, 0.001) // -2 -2 -2 -2 Assert.assertEquals(1514766900000L, glucoseStatus.date) // latest date } @@ -107,9 +107,9 @@ class GlucoseStatusTest : TestBase() { val glucoseStatus: GlucoseStatus = GlucoseStatus(injector).glucoseStatusData!! Assert.assertEquals(100.0, glucoseStatus.glucose, 0.001) // Assert.assertEquals(-10.0, glucoseStatus.delta, 0.001) - Assert.assertEquals(-10.0, glucoseStatus.short_avgdelta, 0.001) - Assert.assertEquals(-10.0, glucoseStatus.avgdelta, 0.001) - Assert.assertEquals(-10.0, glucoseStatus.long_avgdelta, 0.001) + Assert.assertEquals(-10.0, glucoseStatus.shortAvgDelta, 0.001) + Assert.assertEquals(-10.0, glucoseStatus.avgDelta, 0.001) + Assert.assertEquals(-10.0, glucoseStatus.longAvgDelta, 0.001) Assert.assertEquals(1514766900000L, glucoseStatus.date) // latest date } diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPluginTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPluginTest.kt index 6c1f9ec991..391e54f035 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPluginTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPluginTest.kt @@ -13,7 +13,6 @@ import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin import info.nightscout.androidaps.utils.DateUtil -import info.nightscout.androidaps.utils.DefaultValueHelper import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.resources.ResourceHelper @@ -40,7 +39,6 @@ class IobCobCalculatorPluginTest : TestBase() { @Mock lateinit var sensitivityOref1Plugin: SensitivityOref1Plugin @Mock lateinit var sensitivityAAPSPlugin: SensitivityAAPSPlugin @Mock lateinit var sensitivityWeightedAveragePlugin: SensitivityWeightedAveragePlugin - @Mock lateinit var defaultValueHelper: DefaultValueHelper @Mock lateinit var fabricPrivacy: FabricPrivacy @Mock lateinit var dateUtil: DateUtil @Mock lateinit var repository: AppRepository @@ -137,9 +135,9 @@ class IobCobCalculatorPluginTest : TestBase() { iobCobCalculatorPlugin.bgReadings = bgReadingList Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData) iobCobCalculatorPlugin.createBucketedData() - Assert.assertEquals(bgReadingList[0].timestamp, iobCobCalculatorPlugin.bucketedData[0].timestamp) - Assert.assertEquals(bgReadingList[3].timestamp, iobCobCalculatorPlugin.bucketedData[3].timestamp) - Assert.assertEquals(bgReadingList.size.toLong(), iobCobCalculatorPlugin.bucketedData.size.toLong()) + Assert.assertEquals(bgReadingList[0].timestamp, iobCobCalculatorPlugin.bucketedData!![0].timestamp) + Assert.assertEquals(bgReadingList[3].timestamp, iobCobCalculatorPlugin.bucketedData!![3].timestamp) + Assert.assertEquals(bgReadingList.size.toLong(), iobCobCalculatorPlugin.bucketedData!!.size.toLong()) // Missing value should be replaced bgReadingList.clear() @@ -149,9 +147,9 @@ class IobCobCalculatorPluginTest : TestBase() { iobCobCalculatorPlugin.bgReadings = bgReadingList Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData) iobCobCalculatorPlugin.createBucketedData() - Assert.assertEquals(bgReadingList[0].timestamp, iobCobCalculatorPlugin.bucketedData[0].timestamp) - Assert.assertEquals(bgReadingList[2].timestamp, iobCobCalculatorPlugin.bucketedData[3].timestamp) - Assert.assertEquals(bgReadingList.size + 1.toLong(), iobCobCalculatorPlugin.bucketedData.size.toLong()) + Assert.assertEquals(bgReadingList[0].timestamp, iobCobCalculatorPlugin.bucketedData!![0].timestamp) + Assert.assertEquals(bgReadingList[2].timestamp, iobCobCalculatorPlugin.bucketedData!![3].timestamp) + Assert.assertEquals(bgReadingList.size + 1.toLong(), iobCobCalculatorPlugin.bucketedData!!.size.toLong()) // drift should be cleared bgReadingList.clear() @@ -163,11 +161,11 @@ class IobCobCalculatorPluginTest : TestBase() { iobCobCalculatorPlugin.bgReadings = bgReadingList Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData) iobCobCalculatorPlugin.createBucketedData() - Assert.assertEquals(T.mins(20).msecs(), iobCobCalculatorPlugin.bucketedData[0].timestamp) - Assert.assertEquals(T.mins(15).msecs(), iobCobCalculatorPlugin.bucketedData[1].timestamp) - Assert.assertEquals(T.mins(10).msecs(), iobCobCalculatorPlugin.bucketedData[2].timestamp) - Assert.assertEquals(T.mins(5).msecs(), iobCobCalculatorPlugin.bucketedData[3].timestamp) - Assert.assertEquals(bgReadingList.size.toLong(), iobCobCalculatorPlugin.bucketedData.size.toLong()) + Assert.assertEquals(T.mins(20).msecs(), iobCobCalculatorPlugin.bucketedData!![0].timestamp) + Assert.assertEquals(T.mins(15).msecs(), iobCobCalculatorPlugin.bucketedData!![1].timestamp) + Assert.assertEquals(T.mins(10).msecs(), iobCobCalculatorPlugin.bucketedData!![2].timestamp) + Assert.assertEquals(T.mins(5).msecs(), iobCobCalculatorPlugin.bucketedData!![3].timestamp) + Assert.assertEquals(bgReadingList.size.toLong(), iobCobCalculatorPlugin.bucketedData!!.size.toLong()) // bucketed data should return null if not enough bg data bgReadingList.clear() @@ -186,13 +184,13 @@ class IobCobCalculatorPluginTest : TestBase() { iobCobCalculatorPlugin.bgReadings = bgReadingList Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData) iobCobCalculatorPlugin.createBucketedData() - Assert.assertEquals(T.mins(50).msecs(), iobCobCalculatorPlugin.bucketedData[0].timestamp) - Assert.assertEquals(T.mins(20).msecs(), iobCobCalculatorPlugin.bucketedData[6].timestamp) - Assert.assertEquals(7, iobCobCalculatorPlugin.bucketedData.size.toLong()) - Assert.assertEquals(100.0, iobCobCalculatorPlugin.bucketedData[0].value, 1.0) - Assert.assertEquals(90.0, iobCobCalculatorPlugin.bucketedData[1].value, 1.0) - Assert.assertEquals(50.0, iobCobCalculatorPlugin.bucketedData[5].value, 1.0) - Assert.assertEquals(40.0, iobCobCalculatorPlugin.bucketedData[6].value, 1.0) + Assert.assertEquals(T.mins(50).msecs(), iobCobCalculatorPlugin.bucketedData!![0].timestamp) + Assert.assertEquals(T.mins(20).msecs(), iobCobCalculatorPlugin.bucketedData!![6].timestamp) + Assert.assertEquals(7, iobCobCalculatorPlugin.bucketedData!!.size.toLong()) + Assert.assertEquals(100.0, iobCobCalculatorPlugin.bucketedData!![0].value, 1.0) + Assert.assertEquals(90.0, iobCobCalculatorPlugin.bucketedData!![1].value, 1.0) + Assert.assertEquals(50.0, iobCobCalculatorPlugin.bucketedData!![5].value, 1.0) + Assert.assertEquals(40.0, iobCobCalculatorPlugin.bucketedData!![6].value, 1.0) // non 5min data should be reconstructed bgReadingList.clear() @@ -202,16 +200,16 @@ class IobCobCalculatorPluginTest : TestBase() { iobCobCalculatorPlugin.bgReadings = bgReadingList Assert.assertEquals(false, iobCobCalculatorPlugin.isAbout5minData) iobCobCalculatorPlugin.createBucketedData() - Assert.assertEquals(T.mins(50).msecs(), iobCobCalculatorPlugin.bucketedData[0].timestamp) - Assert.assertEquals(T.mins(20).msecs(), iobCobCalculatorPlugin.bucketedData[6].timestamp) - Assert.assertEquals(7, iobCobCalculatorPlugin.bucketedData.size.toLong()) - Assert.assertEquals(100.0, iobCobCalculatorPlugin.bucketedData[0].value, 1.0) - Assert.assertEquals(90.0, iobCobCalculatorPlugin.bucketedData[1].value, 1.0) - Assert.assertEquals(50.0, iobCobCalculatorPlugin.bucketedData[5].value, 1.0) - Assert.assertEquals(40.0, iobCobCalculatorPlugin.bucketedData[6].value, 1.0) + Assert.assertEquals(T.mins(50).msecs(), iobCobCalculatorPlugin.bucketedData!![0].timestamp) + Assert.assertEquals(T.mins(20).msecs(), iobCobCalculatorPlugin.bucketedData!![6].timestamp) + Assert.assertEquals(7, iobCobCalculatorPlugin.bucketedData!!.size.toLong()) + Assert.assertEquals(100.0, iobCobCalculatorPlugin.bucketedData!![0].value, 1.0) + Assert.assertEquals(90.0, iobCobCalculatorPlugin.bucketedData!![1].value, 1.0) + Assert.assertEquals(50.0, iobCobCalculatorPlugin.bucketedData!![5].value, 1.0) + Assert.assertEquals(40.0, iobCobCalculatorPlugin.bucketedData!![6].value, 1.0) //bucketed data should be null if no bg data available - iobCobCalculatorPlugin.bgReadings = null + iobCobCalculatorPlugin.bgReadings = ArrayList() iobCobCalculatorPlugin.createBucketedData() Assert.assertEquals(null, iobCobCalculatorPlugin.bucketedData) @@ -246,11 +244,11 @@ class IobCobCalculatorPluginTest : TestBase() { bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = DateUtil.fromISODateString("2018-09-05T03:50:03Z").time, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = DateUtil.fromISODateString("2018-09-05T03:44:57Z").time, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) iobCobCalculatorPlugin.bgReadings = bgReadingList - iobCobCalculatorPlugin.referenceTime = null + iobCobCalculatorPlugin.referenceTime = -1 Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData) iobCobCalculatorPlugin.createBucketedData() - Assert.assertEquals(DateUtil.fromISODateString("2018-09-05T13:34:57Z").time, iobCobCalculatorPlugin.bucketedData[0].timestamp) - Assert.assertEquals(DateUtil.fromISODateString("2018-09-05T03:44:57Z").time, iobCobCalculatorPlugin.bucketedData[iobCobCalculatorPlugin.bucketedData.size - 1].timestamp) + Assert.assertEquals(DateUtil.fromISODateString("2018-09-05T13:34:57Z").time, iobCobCalculatorPlugin.bucketedData!![0].timestamp) + Assert.assertEquals(DateUtil.fromISODateString("2018-09-05T03:44:57Z").time, iobCobCalculatorPlugin.bucketedData!![iobCobCalculatorPlugin.bucketedData!!.size - 1].timestamp) // 5min 4sec data bgReadingList.clear() diff --git a/app/src/test/java/info/nightscout/androidaps/queue/CommandQueueTest.kt b/app/src/test/java/info/nightscout/androidaps/queue/CommandQueueTest.kt index bccde087ea..03c69c0727 100644 --- a/app/src/test/java/info/nightscout/androidaps/queue/CommandQueueTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/queue/CommandQueueTest.kt @@ -11,6 +11,7 @@ import info.nightscout.androidaps.interfaces.ActivePluginProvider import info.nightscout.androidaps.interfaces.Constraint import info.nightscout.androidaps.interfaces.PumpDescription import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker +import info.nightscout.androidaps.plugins.general.maintenance.LoggerUtils import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin import info.nightscout.androidaps.queue.commands.Command @@ -30,7 +31,9 @@ import org.powermock.modules.junit4.PowerMockRunner import java.util.* @RunWith(PowerMockRunner::class) -@PrepareForTest(ConstraintChecker::class, VirtualPumpPlugin::class, ToastUtils::class, Context::class, TreatmentsPlugin::class, FabricPrivacy::class) +@PrepareForTest( + ConstraintChecker::class, VirtualPumpPlugin::class, ToastUtils::class, Context::class, + TreatmentsPlugin::class, FabricPrivacy::class, LoggerUtils::class) class CommandQueueTest : TestBaseWithProfile() { @Mock lateinit var constraintChecker: ConstraintChecker @@ -39,8 +42,7 @@ class CommandQueueTest : TestBaseWithProfile() { @Mock lateinit var context: Context @Mock lateinit var virtualPumpPlugin: VirtualPumpPlugin @Mock lateinit var sp: SP - - private val buildHelper = BuildHelper(Config()) + @Mock lateinit var loggerUtils: LoggerUtils val injector = HasAndroidInjector { AndroidInjector { @@ -55,7 +57,7 @@ class CommandQueueTest : TestBaseWithProfile() { @Before fun prepare() { - commandQueue = CommandQueue(injector, aapsLogger, rxBus, aapsSchedulers, resourceHelper, constraintChecker, profileFunction, lazyActivePlugin, context, sp, buildHelper, fabricPrivacy) + commandQueue = CommandQueue(injector, aapsLogger, rxBus, aapsSchedulers, resourceHelper, constraintChecker, profileFunction, lazyActivePlugin, context, sp, BuildHelper(Config(), loggerUtils), fabricPrivacy) val pumpDescription = PumpDescription() pumpDescription.basalMinimumRate = 0.1 @@ -65,7 +67,7 @@ class CommandQueueTest : TestBaseWithProfile() { `when`(virtualPumpPlugin.pumpDescription).thenReturn(pumpDescription) `when`(virtualPumpPlugin.isThisProfileSet(anyObject())).thenReturn(false) `when`(activePlugin.activeTreatments).thenReturn(treatmentsPlugin) - `when`(treatmentsPlugin.lastBolusTime).thenReturn(Date(100, 0, 1).time) + `when`(treatmentsPlugin.lastBolusTime).thenReturn(Calendar.getInstance().also { it.set(2000, 0, 1) }.timeInMillis) `when`(profileFunction.getProfile()).thenReturn(validProfile) val bolusConstraint = Constraint(0.0) diff --git a/app/src/test/java/info/nightscout/androidaps/utils/wizard/BolusWizardTest.kt b/app/src/test/java/info/nightscout/androidaps/utils/wizard/BolusWizardTest.kt index 5034861a23..7d6cd613ea 100644 --- a/app/src/test/java/info/nightscout/androidaps/utils/wizard/BolusWizardTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/utils/wizard/BolusWizardTest.kt @@ -31,10 +31,10 @@ import org.powermock.core.classloader.annotations.PrepareForTest import org.powermock.modules.junit4.PowerMockRunner @RunWith(PowerMockRunner::class) -@PrepareForTest(ConstraintChecker::class, VirtualPumpPlugin::class) +@PrepareForTest(ConstraintChecker::class, VirtualPumpPlugin::class, IobCobCalculatorPlugin::class) class BolusWizardTest : TestBase() { - private val PUMP_BOLUS_STEP = 0.1 + private val pumpBolusStep = 0.1 @Mock lateinit var resourceHelper: ResourceHelper @Mock lateinit var profileFunction: ProfileFunction @@ -67,6 +67,7 @@ class BolusWizardTest : TestBase() { } } + @Suppress("SameParameterValue") private fun setupProfile(targetLow: Double, targetHigh: Double, insulinSensitivityFactor: Double, insulinToCarbRatio: Double): Profile { val profile = Mockito.mock(Profile::class.java) `when`(profile.targetLowMgdl).thenReturn(targetLow) @@ -81,7 +82,7 @@ class BolusWizardTest : TestBase() { `when`(treatmentsPlugin.lastCalculationTempBasals).thenReturn(IobTotal(System.currentTimeMillis())) `when`(activePlugin.activePump).thenReturn(virtualPumpPlugin) val pumpDescription = PumpDescription() - pumpDescription.bolusStep = PUMP_BOLUS_STEP + pumpDescription.bolusStep = pumpBolusStep `when`(virtualPumpPlugin.pumpDescription).thenReturn(pumpDescription) Mockito.doAnswer { invocation: InvocationOnMock -> @@ -94,9 +95,9 @@ class BolusWizardTest : TestBase() { /** Should calculate the same bolus when different blood glucose but both in target range */ fun shouldCalculateTheSameBolusWhenBGsInRange() { val profile = setupProfile(4.0, 8.0, 20.0, 12.0) - var bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 4.2, 0.0, 100.0, true, true, true, true, false, false, false, false) + var bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 4.2, 0.0, 100.0, useBg = true, useCob = true, includeBolusIOB = true, includeBasalIOB = true, useSuperBolus = false, useTT = false, useTrend = false, useAlarm = false) val bolusForBg42 = bw.calculatedTotalInsulin - bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 5.4, 0.0, 100.0, true, true, true, true, false, false, false, false) + bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 5.4, 0.0, 100.0, useBg = true, useCob = true, includeBolusIOB = true, includeBasalIOB = true, useSuperBolus = false, useTT = false, useTrend = false, useAlarm = false) val bolusForBg54 = bw.calculatedTotalInsulin Assert.assertEquals(bolusForBg42, bolusForBg54, 0.01) } @@ -104,9 +105,9 @@ class BolusWizardTest : TestBase() { @Test fun shouldCalculateHigherBolusWhenHighBG() { val profile = setupProfile(4.0, 8.0, 20.0, 12.0) - var bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 9.8, 0.0, 100.0, true, true, true, true, false, false, false, false) + var bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 9.8, 0.0, 100.0, useBg = true, useCob = true, includeBolusIOB = true, includeBasalIOB = true, useSuperBolus = false, useTT = false, useTrend = false, useAlarm = false) val bolusForHighBg = bw.calculatedTotalInsulin - bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 5.4, 0.0, 100.0, true, true, true, true, false, false, false, false) + bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 5.4, 0.0, 100.0, useBg = true, useCob = true, includeBolusIOB = true, includeBasalIOB = true, useSuperBolus = false, useTT = false, useTrend = false, useAlarm = false) val bolusForBgInRange = bw.calculatedTotalInsulin Assert.assertTrue(bolusForHighBg > bolusForBgInRange) } @@ -114,9 +115,9 @@ class BolusWizardTest : TestBase() { @Test fun shouldCalculateLowerBolusWhenLowBG() { val profile = setupProfile(4.0, 8.0, 20.0, 12.0) - var bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 3.6, 0.0, 100.0, true, true, true, true, false, false, false, false) + var bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 3.6, 0.0, 100.0, useBg = true, useCob = true, includeBolusIOB = true, includeBasalIOB = true, useSuperBolus = false, useTT = false, useTrend = false, useAlarm = false) val bolusForLowBg = bw.calculatedTotalInsulin - bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 5.4, 0.0, 100.0, true, true, true, true, false, false, false, false) + bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 5.4, 0.0, 100.0, useBg = true, useCob = true, includeBolusIOB = true, includeBasalIOB = true, useSuperBolus = false, useTT = false, useTrend = false, useAlarm = false) val bolusForBgInRange = bw.calculatedTotalInsulin Assert.assertTrue(bolusForLowBg < bolusForBgInRange) } diff --git a/build.gradle b/build.gradle index 89a1b9235a..8e5b1e8207 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ buildscript { dagger_version = '2.32' coroutinesVersion = '1.4.1' activityVersion = '1.2.0' - fragmentktx_version = '1.3.0-rc02' + fragmentktx_version = '1.3.0' ormLiteVersion = '4.46' nav_version = '2.3.3' appcompat_version = '1.2.0' diff --git a/core/src/main/java/info/nightscout/androidaps/Constants.java b/core/src/main/java/info/nightscout/androidaps/Constants.java index 355b560ee8..b0daa4fe78 100644 --- a/core/src/main/java/info/nightscout/androidaps/Constants.java +++ b/core/src/main/java/info/nightscout/androidaps/Constants.java @@ -46,9 +46,9 @@ public class Constants { public static final int defaultEatingSoonTTDuration = 45; // min public static final double defaultEatingSoonTTmgdl = 90d; public static final double defaultEatingSoonTTmmol = 5d; - public static final int defaultHypoTTDuration = 30; // min - public static final double defaultHypoTTmgdl = 120d; - public static final double defaultHypoTTmmol = 6.5d; + public static final int defaultHypoTTDuration = 60; // min + public static final double defaultHypoTTmgdl = 160d; + public static final double defaultHypoTTmmol = 8d; public static final double MIN_TT_MGDL = 72d; public static final double MAX_TT_MGDL = 180d; diff --git a/core/src/main/java/info/nightscout/androidaps/interfaces/ConstraintsInterface.kt b/core/src/main/java/info/nightscout/androidaps/interfaces/ConstraintsInterface.kt index 1afa1f4738..7142bf251a 100644 --- a/core/src/main/java/info/nightscout/androidaps/interfaces/ConstraintsInterface.kt +++ b/core/src/main/java/info/nightscout/androidaps/interfaces/ConstraintsInterface.kt @@ -7,78 +7,20 @@ import info.nightscout.androidaps.data.Profile */ interface ConstraintsInterface { - @JvmDefault - fun isLoopInvocationAllowed(value: Constraint): Constraint { - return value - } - - @JvmDefault - fun isClosedLoopAllowed(value: Constraint): Constraint { - return value - } - - @JvmDefault - fun isAutosensModeEnabled(value: Constraint): Constraint { - return value - } - - @JvmDefault - fun isAMAModeEnabled(value: Constraint): Constraint { - return value - } - - @JvmDefault - fun isSMBModeEnabled(value: Constraint): Constraint { - return value - } - - @JvmDefault - fun isUAMEnabled(value: Constraint): Constraint { - return value - } - - @JvmDefault - fun isAdvancedFilteringEnabled(value: Constraint): Constraint { - return value - } - - @JvmDefault - fun isSuperBolusEnabled(value: Constraint): Constraint { - return value - } - - @JvmDefault - fun applyBasalConstraints(absoluteRate: Constraint, profile: Profile): Constraint { - return absoluteRate - } - - @JvmDefault - fun applyBasalPercentConstraints(percentRate: Constraint, profile: Profile): Constraint { - return percentRate - } - - @JvmDefault - fun applyBolusConstraints(insulin: Constraint): Constraint { - return insulin - } - - @JvmDefault - fun applyExtendedBolusConstraints(insulin: Constraint): Constraint { - return insulin - } - - @JvmDefault - fun applyCarbsConstraints(carbs: Constraint): Constraint { - return carbs - } - - @JvmDefault - fun applyMaxIOBConstraints(maxIob: Constraint): Constraint { - return maxIob - } - - @JvmDefault - fun isAutomationEnabled(value: Constraint): Constraint { - return value - } + @JvmDefault fun isLoopInvocationAllowed(value: Constraint): Constraint = value + @JvmDefault fun isClosedLoopAllowed(value: Constraint): Constraint = value + @JvmDefault fun isLgsAllowed(value: Constraint): Constraint = value + @JvmDefault fun isAutosensModeEnabled(value: Constraint): Constraint = value + @JvmDefault fun isAMAModeEnabled(value: Constraint): Constraint = value + @JvmDefault fun isSMBModeEnabled(value: Constraint): Constraint = value + @JvmDefault fun isUAMEnabled(value: Constraint): Constraint = value + @JvmDefault fun isAdvancedFilteringEnabled(value: Constraint): Constraint = value + @JvmDefault fun isSuperBolusEnabled(value: Constraint): Constraint = value + @JvmDefault fun applyBasalConstraints(absoluteRate: Constraint, profile: Profile): Constraint = absoluteRate + @JvmDefault fun applyBasalPercentConstraints(percentRate: Constraint, profile: Profile): Constraint = percentRate + @JvmDefault fun applyBolusConstraints(insulin: Constraint): Constraint = insulin + @JvmDefault fun applyExtendedBolusConstraints(insulin: Constraint): Constraint = insulin + @JvmDefault fun applyCarbsConstraints(carbs: Constraint): Constraint = carbs + @JvmDefault fun applyMaxIOBConstraints(maxIob: Constraint): Constraint = maxIob + @JvmDefault fun isAutomationEnabled(value: Constraint): Constraint = value } \ No newline at end of file