commit
c84161b83f
|
@ -131,6 +131,7 @@ class ProfileHelperActivity : NoSplashAppCompatActivity() {
|
||||||
|
|
||||||
// Default profile
|
// Default profile
|
||||||
binding.copytolocalprofile.setOnClickListener {
|
binding.copytolocalprofile.setOnClickListener {
|
||||||
|
storeValues()
|
||||||
val age = ageUsed[tabSelected]
|
val age = ageUsed[tabSelected]
|
||||||
val weight = weightUsed[tabSelected]
|
val weight = weightUsed[tabSelected]
|
||||||
val tdd = tddUsed[tabSelected]
|
val tdd = tddUsed[tabSelected]
|
||||||
|
|
|
@ -22,7 +22,6 @@ import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
|
||||||
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
|
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
|
||||||
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin
|
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin
|
||||||
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
|
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.queue.Callback
|
||||||
import info.nightscout.androidaps.utils.FabricPrivacy
|
import info.nightscout.androidaps.utils.FabricPrivacy
|
||||||
import info.nightscout.androidaps.utils.ToastUtils
|
import info.nightscout.androidaps.utils.ToastUtils
|
||||||
|
@ -42,7 +41,6 @@ class LoopDialog : DaggerDialogFragment() {
|
||||||
@Inject lateinit var resourceHelper: ResourceHelper
|
@Inject lateinit var resourceHelper: ResourceHelper
|
||||||
@Inject lateinit var profileFunction: ProfileFunction
|
@Inject lateinit var profileFunction: ProfileFunction
|
||||||
@Inject lateinit var loopPlugin: LoopPlugin
|
@Inject lateinit var loopPlugin: LoopPlugin
|
||||||
@Inject lateinit var objectivesPlugin: ObjectivesPlugin
|
|
||||||
@Inject lateinit var activePlugin: ActivePluginProvider
|
@Inject lateinit var activePlugin: ActivePluginProvider
|
||||||
@Inject lateinit var constraintChecker: ConstraintChecker
|
@Inject lateinit var constraintChecker: ConstraintChecker
|
||||||
@Inject lateinit var commandQueue: CommandQueueProvider
|
@Inject lateinit var commandQueue: CommandQueueProvider
|
||||||
|
@ -107,7 +105,7 @@ class LoopDialog : DaggerDialogFragment() {
|
||||||
binding.cancel.setOnClickListener { dismiss() }
|
binding.cancel.setOnClickListener { dismiss() }
|
||||||
|
|
||||||
refreshDialog = Runnable {
|
refreshDialog = Runnable {
|
||||||
scheduleUpdateGUI("refreshDialog")
|
scheduleUpdateGUI()
|
||||||
loopHandler.postDelayed(refreshDialog, 15 * 1000L)
|
loopHandler.postDelayed(refreshDialog, 15 * 1000L)
|
||||||
}
|
}
|
||||||
loopHandler.postDelayed(refreshDialog, 15 * 1000L)
|
loopHandler.postDelayed(refreshDialog, 15 * 1000L)
|
||||||
|
@ -122,11 +120,11 @@ class LoopDialog : DaggerDialogFragment() {
|
||||||
|
|
||||||
var task: Runnable? = null
|
var task: Runnable? = null
|
||||||
|
|
||||||
private fun scheduleUpdateGUI(from: String) {
|
private fun scheduleUpdateGUI() {
|
||||||
class UpdateRunnable : Runnable {
|
class UpdateRunnable : Runnable {
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
updateGUI(from)
|
updateGUI("refreshDialog")
|
||||||
task = null
|
task = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,8 +138,8 @@ class LoopDialog : DaggerDialogFragment() {
|
||||||
if (_binding == null) return
|
if (_binding == null) return
|
||||||
aapsLogger.debug("UpdateGUI from $from")
|
aapsLogger.debug("UpdateGUI from $from")
|
||||||
val pumpDescription: PumpDescription = activePlugin.activePump.pumpDescription
|
val pumpDescription: PumpDescription = activePlugin.activePump.pumpDescription
|
||||||
val closedLoopAllowed = objectivesPlugin.isClosedLoopAllowed(Constraint(true))
|
val closedLoopAllowed = constraintChecker.isClosedLoopAllowed(Constraint(true))
|
||||||
val lgsEnabled = objectivesPlugin.isLgsAllowed(Constraint(true))
|
val lgsEnabled = constraintChecker.isLgsAllowed(Constraint(true))
|
||||||
val apsMode = sp.getString(R.string.key_aps_mode, "open")
|
val apsMode = sp.getString(R.string.key_aps_mode, "open")
|
||||||
if (profileFunction.isProfileValid("LoopDialogUpdateGUI")) {
|
if (profileFunction.isProfileValid("LoopDialogUpdateGUI")) {
|
||||||
if (loopPlugin.isEnabled(PluginType.LOOP)) {
|
if (loopPlugin.isEnabled(PluginType.LOOP)) {
|
||||||
|
|
|
@ -270,7 +270,7 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() {
|
||||||
val end = start + T.hours(rangeToDisplay.toLong()).msecs()
|
val end = start + T.hours(rangeToDisplay.toLong()).msecs()
|
||||||
iobCobCalculatorPluginHistory.stopCalculation(from)
|
iobCobCalculatorPluginHistory.stopCalculation(from)
|
||||||
iobCobCalculatorPluginHistory.clearCache()
|
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
|
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]
|
val 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 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.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)
|
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
|
// set manual x bounds to have nice steps
|
||||||
graphData.setNumVerticalLables()
|
graphData.setNumVerticalLabels()
|
||||||
graphData.formatAxis(fromTime, toTime)
|
graphData.formatAxis(fromTime, toTime)
|
||||||
}
|
}
|
||||||
// finally enforce drawing of graphs in UI thread
|
// finally enforce drawing of graphs in UI thread
|
||||||
|
|
|
@ -61,6 +61,7 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader
|
||||||
var scriptDebug = ""
|
var scriptDebug = ""
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
@Suppress("SpellCheckingInspection")
|
||||||
operator fun invoke(): DetermineBasalResultAMA? {
|
operator fun invoke(): DetermineBasalResultAMA? {
|
||||||
aapsLogger.debug(LTag.APS, ">>> Invoking determine_basal <<<")
|
aapsLogger.debug(LTag.APS, ">>> Invoking determine_basal <<<")
|
||||||
aapsLogger.debug(LTag.APS, "Glucose status: " + glucoseStatus.toString().also { glucoseStatusParam = it })
|
aapsLogger.debug(LTag.APS, "Glucose status: " + glucoseStatus.toString().also { glucoseStatusParam = it })
|
||||||
|
@ -142,6 +143,7 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader
|
||||||
return determineBasalResultAMA
|
return determineBasalResultAMA
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("SpellCheckingInspection")
|
||||||
@Throws(JSONException::class) fun setData(profile: Profile,
|
@Throws(JSONException::class) fun setData(profile: Profile,
|
||||||
maxIob: Double,
|
maxIob: Double,
|
||||||
maxBasal: Double,
|
maxBasal: Double,
|
||||||
|
@ -149,7 +151,7 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader
|
||||||
maxBg: Double,
|
maxBg: Double,
|
||||||
targetBg: Double,
|
targetBg: Double,
|
||||||
basalRate: Double,
|
basalRate: Double,
|
||||||
iobArray: Array<IobTotal?>?,
|
iobArray: Array<IobTotal>,
|
||||||
glucoseStatus: GlucoseStatus,
|
glucoseStatus: GlucoseStatus,
|
||||||
mealData: MealData,
|
mealData: MealData,
|
||||||
autosensDataRatio: Double,
|
autosensDataRatio: Double,
|
||||||
|
@ -196,12 +198,12 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader
|
||||||
this.glucoseStatus = JSONObject()
|
this.glucoseStatus = JSONObject()
|
||||||
this.glucoseStatus.put("glucose", glucoseStatus.glucose)
|
this.glucoseStatus.put("glucose", glucoseStatus.glucose)
|
||||||
if (sp.getBoolean(R.string.key_always_use_shortavg, false)) {
|
if (sp.getBoolean(R.string.key_always_use_shortavg, false)) {
|
||||||
this.glucoseStatus.put("delta", glucoseStatus.short_avgdelta)
|
this.glucoseStatus.put("delta", glucoseStatus.shortAvgDelta)
|
||||||
} else {
|
} else {
|
||||||
this.glucoseStatus.put("delta", glucoseStatus.delta)
|
this.glucoseStatus.put("delta", glucoseStatus.delta)
|
||||||
}
|
}
|
||||||
this.glucoseStatus.put("short_avgdelta", glucoseStatus.short_avgdelta)
|
this.glucoseStatus.put("short_avgdelta", glucoseStatus.shortAvgDelta)
|
||||||
this.glucoseStatus.put("long_avgdelta", glucoseStatus.long_avgdelta)
|
this.glucoseStatus.put("long_avgdelta", glucoseStatus.longAvgDelta)
|
||||||
this.mealData = JSONObject()
|
this.mealData = JSONObject()
|
||||||
this.mealData.put("carbs", mealData.carbs)
|
this.mealData.put("carbs", mealData.carbs)
|
||||||
this.mealData.put("boluses", mealData.boluses)
|
this.mealData.put("boluses", mealData.boluses)
|
||||||
|
|
|
@ -241,12 +241,12 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader:
|
||||||
mGlucoseStatus.put("glucose", glucoseStatus.glucose)
|
mGlucoseStatus.put("glucose", glucoseStatus.glucose)
|
||||||
mGlucoseStatus.put("noise", glucoseStatus.noise)
|
mGlucoseStatus.put("noise", glucoseStatus.noise)
|
||||||
if (sp.getBoolean(R.string.key_always_use_shortavg, false)) {
|
if (sp.getBoolean(R.string.key_always_use_shortavg, false)) {
|
||||||
mGlucoseStatus.put("delta", glucoseStatus.short_avgdelta)
|
mGlucoseStatus.put("delta", glucoseStatus.shortAvgDelta)
|
||||||
} else {
|
} else {
|
||||||
mGlucoseStatus.put("delta", glucoseStatus.delta)
|
mGlucoseStatus.put("delta", glucoseStatus.delta)
|
||||||
}
|
}
|
||||||
mGlucoseStatus.put("short_avgdelta", glucoseStatus.short_avgdelta)
|
mGlucoseStatus.put("short_avgdelta", glucoseStatus.shortAvgDelta)
|
||||||
mGlucoseStatus.put("long_avgdelta", glucoseStatus.long_avgdelta)
|
mGlucoseStatus.put("long_avgdelta", glucoseStatus.longAvgDelta)
|
||||||
mGlucoseStatus.put("date", glucoseStatus.date)
|
mGlucoseStatus.put("date", glucoseStatus.date)
|
||||||
this.mealData.put("carbs", mealData.carbs)
|
this.mealData.put("carbs", mealData.carbs)
|
||||||
this.mealData.put("boluses", mealData.boluses)
|
this.mealData.put("boluses", mealData.boluses)
|
||||||
|
|
|
@ -165,7 +165,7 @@ class ObjectivesPlugin @Inject constructor(
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isLgsAllowed(value: Constraint<Boolean>): Constraint<Boolean> {
|
override fun isLgsAllowed(value: Constraint<Boolean>): Constraint<Boolean> {
|
||||||
if (!objectives[MAXBASAL_OBJECTIVE].isStarted)
|
if (!objectives[MAXBASAL_OBJECTIVE].isStarted)
|
||||||
value.set(aapsLogger, false, String.format(resourceHelper.gs(R.string.objectivenotstarted), MAXBASAL_OBJECTIVE + 1), this)
|
value.set(aapsLogger, false, String.format(resourceHelper.gs(R.string.objectivenotstarted), MAXBASAL_OBJECTIVE + 1), this)
|
||||||
return value
|
return value
|
||||||
|
|
|
@ -73,8 +73,8 @@ class TriggerDelta(injector: HasAndroidInjector) : Trigger(injector) {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
val calculatedDelta = when (delta.deltaType) {
|
val calculatedDelta = when (delta.deltaType) {
|
||||||
DeltaType.SHORT_AVERAGE -> glucoseStatus.short_avgdelta
|
DeltaType.SHORT_AVERAGE -> glucoseStatus.shortAvgDelta
|
||||||
DeltaType.LONG_AVERAGE -> glucoseStatus.long_avgdelta
|
DeltaType.LONG_AVERAGE -> glucoseStatus.longAvgDelta
|
||||||
else -> glucoseStatus.delta
|
else -> glucoseStatus.delta
|
||||||
}
|
}
|
||||||
if (comparator.value.check(calculatedDelta, Profile.toMgdl(delta.value, units))) {
|
if (comparator.value.check(calculatedDelta, Profile.toMgdl(delta.value, units))) {
|
||||||
|
|
|
@ -130,7 +130,7 @@ class DataBroadcastPlugin @Inject constructor(
|
||||||
bundle.putString("units", profileFunction.getUnits()) // units used in AAPS "mg/dl" or "mmol"
|
bundle.putString("units", profileFunction.getUnits()) // units used in AAPS "mg/dl" or "mmol"
|
||||||
bundle.putString("slopeArrow", lastBG.trendArrow.text) // direction arrow as string
|
bundle.putString("slopeArrow", lastBG.trendArrow.text) // direction arrow as string
|
||||||
bundle.putDouble("deltaMgdl", glucoseStatus.delta) // bg delta in mgdl
|
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("high", defaultValueHelper.determineHighLine()) // predefined top value of in range (green area)
|
||||||
bundle.putDouble("low", defaultValueHelper.determineLowLine()) // predefined bottom value of in range
|
bundle.putDouble("low", defaultValueHelper.determineLowLine()) // predefined bottom value of in range
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,7 +34,8 @@ class MaintenancePlugin @Inject constructor(
|
||||||
private val nsSettingsStatus: NSSettingsStatus,
|
private val nsSettingsStatus: NSSettingsStatus,
|
||||||
aapsLogger: AAPSLogger,
|
aapsLogger: AAPSLogger,
|
||||||
private val buildHelper: BuildHelper,
|
private val buildHelper: BuildHelper,
|
||||||
private val config: Config
|
private val config: Config,
|
||||||
|
private val loggerUtils: LoggerUtils
|
||||||
) : PluginBase(PluginDescription()
|
) : PluginBase(PluginDescription()
|
||||||
.mainType(PluginType.GENERAL)
|
.mainType(PluginType.GENERAL)
|
||||||
.fragmentClass(MaintenanceFragment::class.java.name)
|
.fragmentClass(MaintenanceFragment::class.java.name)
|
||||||
|
@ -51,8 +52,7 @@ class MaintenancePlugin @Inject constructor(
|
||||||
fun sendLogs() {
|
fun sendLogs() {
|
||||||
val recipient = sp.getString(R.string.key_maintenance_logs_email, "logs@androidaps.org")
|
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 amount = sp.getInt(R.string.key_maintenance_logs_amount, 2)
|
||||||
val logDirectory = LoggerUtils.getLogDirectory()
|
val logs = getLogFiles(amount)
|
||||||
val logs = getLogFiles(logDirectory, amount)
|
|
||||||
val zipDir = context.getExternalFilesDir("exports")
|
val zipDir = context.getExternalFilesDir("exports")
|
||||||
val zipFile = File(zipDir, constructName())
|
val zipFile = File(zipDir, constructName())
|
||||||
aapsLogger.debug("zipFile: ${zipFile.absolutePath}")
|
aapsLogger.debug("zipFile: ${zipFile.absolutePath}")
|
||||||
|
@ -66,8 +66,7 @@ class MaintenancePlugin @Inject constructor(
|
||||||
//todo replace this with a call on startup of the application, specifically to remove
|
//todo replace this with a call on startup of the application, specifically to remove
|
||||||
// unnecessary garbage from the log exports
|
// unnecessary garbage from the log exports
|
||||||
fun deleteLogs() {
|
fun deleteLogs() {
|
||||||
LoggerUtils.getLogDirectory()?.let { logDirectory ->
|
val logDir = File(loggerUtils.logDirectory)
|
||||||
val logDir = File(logDirectory)
|
|
||||||
val files = logDir.listFiles { _: File?, name: String ->
|
val files = logDir.listFiles { _: File?, name: String ->
|
||||||
(name.startsWith("AndroidAPS") && name.endsWith(".zip"))
|
(name.startsWith("AndroidAPS") && name.endsWith(".zip"))
|
||||||
}
|
}
|
||||||
|
@ -81,7 +80,7 @@ class MaintenancePlugin @Inject constructor(
|
||||||
file.delete()
|
file.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val exportDir = File(logDirectory, "exports")
|
val exportDir = File(loggerUtils.logDirectory, "exports")
|
||||||
if (exportDir.exists()) {
|
if (exportDir.exists()) {
|
||||||
val expFiles = exportDir.listFiles()
|
val expFiles = exportDir.listFiles()
|
||||||
for (file in expFiles) {
|
for (file in expFiles) {
|
||||||
|
@ -90,7 +89,6 @@ class MaintenancePlugin @Inject constructor(
|
||||||
exportDir.delete()
|
exportDir.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns a list of log files. The number of returned logs is given via the amount
|
* returns a list of log files. The number of returned logs is given via the amount
|
||||||
|
@ -98,17 +96,16 @@ class MaintenancePlugin @Inject constructor(
|
||||||
*
|
*
|
||||||
* The log files are sorted by the name descending.
|
* The log files are sorted by the name descending.
|
||||||
*
|
*
|
||||||
* @param directory
|
|
||||||
* @param amount
|
* @param amount
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
fun getLogFiles(directory: String, amount: Int): List<File> {
|
fun getLogFiles(amount: Int): List<File> {
|
||||||
aapsLogger.debug("getting $amount logs from directory $directory")
|
aapsLogger.debug("getting $amount logs from directory ${loggerUtils.logDirectory}")
|
||||||
val logDir = File(directory)
|
val logDir = File(loggerUtils.logDirectory)
|
||||||
val files = logDir.listFiles { _: File?, name: String ->
|
val files = logDir.listFiles { _: File?, name: String ->
|
||||||
(name.startsWith("AndroidAPS")
|
(name.startsWith("AndroidAPS")
|
||||||
&& (name.endsWith(".log")
|
&& (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) }
|
Arrays.sort(files) { f1: File, f2: File -> f2.name.compareTo(f1.name) }
|
||||||
val result = listOf(*files)
|
val result = listOf(*files)
|
||||||
|
@ -139,7 +136,7 @@ class MaintenancePlugin @Inject constructor(
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private fun constructName(): String {
|
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<File>) {
|
private fun zip(zipFile: File?, files: List<File>) {
|
||||||
|
|
|
@ -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.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units)
|
||||||
binding.infoLayout.deltaLarge.setTextColor(color)
|
binding.infoLayout.deltaLarge.setTextColor(color)
|
||||||
binding.infoLayout.delta.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units)
|
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.avgDelta.text = Profile.toSignedUnitsString(glucoseStatus.shortAvgDelta, glucoseStatus.shortAvgDelta * Constants.MGDL_TO_MMOLL, units)
|
||||||
binding.infoLayout.longAvgDelta.text = Profile.toSignedUnitsString(glucoseStatus.long_avgdelta, glucoseStatus.long_avgdelta * Constants.MGDL_TO_MMOLL, units)
|
binding.infoLayout.longAvgDelta.text = Profile.toSignedUnitsString(glucoseStatus.longAvgDelta, glucoseStatus.longAvgDelta * Constants.MGDL_TO_MMOLL, units)
|
||||||
} else {
|
} else {
|
||||||
binding.infoLayout.delta.text = "Δ " + resourceHelper.gs(R.string.notavailable)
|
binding.infoLayout.delta.text = "Δ " + resourceHelper.gs(R.string.notavailable)
|
||||||
binding.infoLayout.avgDelta.text = ""
|
binding.infoLayout.avgDelta.text = ""
|
||||||
|
@ -861,7 +861,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
|
||||||
graphData.addTreatments(fromTime, endTime)
|
graphData.addTreatments(fromTime, endTime)
|
||||||
|
|
||||||
// set manual x bounds to have nice steps
|
// set manual x bounds to have nice steps
|
||||||
graphData.setNumVerticalLables()
|
graphData.setNumVerticalLabels()
|
||||||
graphData.formatAxis(fromTime, endTime)
|
graphData.formatAxis(fromTime, endTime)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -88,7 +88,7 @@ class GraphData(
|
||||||
addSeries(PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] }))
|
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()
|
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) {
|
fun addActivity(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) {
|
||||||
val actArrayHist: MutableList<ScaledDataPoint> = ArrayList()
|
val actArrayHist: MutableList<ScaledDataPoint> = ArrayList()
|
||||||
val actArrayPred: MutableList<ScaledDataPoint> = ArrayList()
|
val actArrayPrediction: MutableList<ScaledDataPoint> = ArrayList()
|
||||||
val now = System.currentTimeMillis().toDouble()
|
val now = System.currentTimeMillis().toDouble()
|
||||||
val actScale = Scale()
|
val actScale = Scale()
|
||||||
var total: IobTotal
|
var total: IobTotal
|
||||||
|
@ -305,7 +305,7 @@ class GraphData(
|
||||||
}
|
}
|
||||||
total = iobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time, profile)
|
total = iobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time, profile)
|
||||||
val act: Double = total.activity
|
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))
|
maxIAValue = max(maxIAValue, abs(act))
|
||||||
time += 5 * 60 * 1000L
|
time += 5 * 60 * 1000L
|
||||||
}
|
}
|
||||||
|
@ -314,7 +314,7 @@ class GraphData(
|
||||||
it.color = resourceHelper.gc(R.color.activity)
|
it.color = resourceHelper.gc(R.color.activity)
|
||||||
it.thickness = 3
|
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 ->
|
it.setCustomPaint(Paint().also { paint ->
|
||||||
paint.style = Paint.Style.STROKE
|
paint.style = Paint.Style.STROKE
|
||||||
paint.strokeWidth = 3f
|
paint.strokeWidth = 3f
|
||||||
|
@ -332,7 +332,7 @@ class GraphData(
|
||||||
//Function below show -BGI to be able to compare curves with deviations
|
//Function below show -BGI to be able to compare curves with deviations
|
||||||
fun addMinusBGI(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double, devBgiScale: Boolean) {
|
fun addMinusBGI(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double, devBgiScale: Boolean) {
|
||||||
val bgiArrayHist: MutableList<ScaledDataPoint> = ArrayList()
|
val bgiArrayHist: MutableList<ScaledDataPoint> = ArrayList()
|
||||||
val bgiArrayPred: MutableList<ScaledDataPoint> = ArrayList()
|
val bgiArrayPrediction: MutableList<ScaledDataPoint> = ArrayList()
|
||||||
val now = System.currentTimeMillis().toDouble()
|
val now = System.currentTimeMillis().toDouble()
|
||||||
val bgiScale = Scale()
|
val bgiScale = Scale()
|
||||||
var total: IobTotal
|
var total: IobTotal
|
||||||
|
@ -348,7 +348,7 @@ class GraphData(
|
||||||
|
|
||||||
total = iobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time, profile)
|
total = iobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time, profile)
|
||||||
val bgi: Double = total.activity * profile.getIsfMgdl(time) * 5.0
|
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))
|
maxBGIValue = max(maxBGIValue, max(abs(bgi), deviation))
|
||||||
time += 5 * 60 * 1000L
|
time += 5 * 60 * 1000L
|
||||||
}
|
}
|
||||||
|
@ -357,7 +357,7 @@ class GraphData(
|
||||||
it.color = resourceHelper.gc(R.color.bgi)
|
it.color = resourceHelper.gc(R.color.bgi)
|
||||||
it.thickness = 3
|
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 ->
|
it.setCustomPaint(Paint().also { paint ->
|
||||||
paint.style = Paint.Style.STROKE
|
paint.style = Paint.Style.STROKE
|
||||||
paint.strokeWidth = 3f
|
paint.strokeWidth = 3f
|
||||||
|
@ -386,7 +386,7 @@ class GraphData(
|
||||||
var absIob = 0.0
|
var absIob = 0.0
|
||||||
if (profile != null) {
|
if (profile != null) {
|
||||||
iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time, profile).iob
|
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.02) {
|
||||||
if (abs(lastIob - iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, iobScale))
|
if (abs(lastIob - iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, iobScale))
|
||||||
|
@ -406,22 +406,22 @@ class GraphData(
|
||||||
val autosensData = iobCobCalculatorPlugin.getLastAutosensDataSynchronized("GraphData")
|
val autosensData = iobCobCalculatorPlugin.getLastAutosensDataSynchronized("GraphData")
|
||||||
val lastAutosensResult = autosensData?.autosensResult ?: AutosensResult()
|
val lastAutosensResult = autosensData?.autosensResult ?: AutosensResult()
|
||||||
val isTempTarget = treatmentsPlugin.getTempTargetFromHistory(System.currentTimeMillis()) != null
|
val isTempTarget = treatmentsPlugin.getTempTargetFromHistory(System.currentTimeMillis()) != null
|
||||||
val iobPred: MutableList<DataPointWithLabelInterface> = ArrayList()
|
val iobPrediction: MutableList<DataPointWithLabelInterface> = ArrayList()
|
||||||
val iobPredArray = iobCobCalculatorPlugin.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget)
|
val iobPredictionArray = iobCobCalculatorPlugin.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget)
|
||||||
for (i in iobPredArray) {
|
for (i in iobPredictionArray) {
|
||||||
iobPred.add(i.setColor(resourceHelper.gc(R.color.iobPredAS)))
|
iobPrediction.add(i.setColor(resourceHelper.gc(R.color.iobPredAS)))
|
||||||
maxIobValueFound = max(maxIobValueFound, abs(i.iob))
|
maxIobValueFound = max(maxIobValueFound, abs(i.iob))
|
||||||
}
|
}
|
||||||
addSeries(PointsWithLabelGraphSeries(Array(iobPred.size) { i -> iobPred[i] }))
|
addSeries(PointsWithLabelGraphSeries(Array(iobPrediction.size) { i -> iobPrediction[i] }))
|
||||||
val iobPred2: MutableList<DataPointWithLabelInterface> = ArrayList()
|
val iobPrediction2: MutableList<DataPointWithLabelInterface> = ArrayList()
|
||||||
val iobPredArray2 = iobCobCalculatorPlugin.calculateIobArrayForSMB(AutosensResult(), SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget)
|
val iobPredictionArray2 = iobCobCalculatorPlugin.calculateIobArrayForSMB(AutosensResult(), SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget)
|
||||||
for (i in iobPredArray2) {
|
for (i in iobPredictionArray2) {
|
||||||
iobPred2.add(i.setColor(resourceHelper.gc(R.color.iobPred)))
|
iobPrediction2.add(i.setColor(resourceHelper.gc(R.color.iobPred)))
|
||||||
maxIobValueFound = max(maxIobValueFound, abs(i.iob))
|
maxIobValueFound = max(maxIobValueFound, abs(i.iob))
|
||||||
}
|
}
|
||||||
addSeries(PointsWithLabelGraphSeries(Array(iobPred2.size) { i -> iobPred2[i] }))
|
addSeries(PointsWithLabelGraphSeries(Array(iobPrediction2.size) { i -> iobPrediction2[i] }))
|
||||||
aapsLogger.debug(LTag.AUTOSENS, "IOB pred for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredArray))
|
aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredictionArray))
|
||||||
aapsLogger.debug(LTag.AUTOSENS, "IOB pred for AS=" + DecimalFormatter.to2Decimal(1.0) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredArray2))
|
aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(1.0) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredictionArray2))
|
||||||
}
|
}
|
||||||
if (useForScale) {
|
if (useForScale) {
|
||||||
maxY = maxIobValueFound
|
maxY = maxIobValueFound
|
||||||
|
@ -442,7 +442,7 @@ class GraphData(
|
||||||
while (time <= toTime) {
|
while (time <= toTime) {
|
||||||
val profile = profileFunction.getProfile(time)
|
val profile = profileFunction.getProfile(time)
|
||||||
var iob = 0.0
|
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.02) {
|
||||||
if (abs(lastIob - iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, iobScale))
|
if (abs(lastIob - iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, iobScale))
|
||||||
iobArray.add(ScaledDataPoint(time, iob, iobScale))
|
iobArray.add(ScaledDataPoint(time, iob, iobScale))
|
||||||
|
|
|
@ -140,7 +140,7 @@ class PersistentNotificationPlugin @Inject constructor(
|
||||||
line1 = line1aa
|
line1 = line1aa
|
||||||
if (glucoseStatus != null) {
|
if (glucoseStatus != null) {
|
||||||
line1 += (" Δ" + Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units)
|
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
|
line1aa += " " + lastBG.trendArrow.symbol
|
||||||
} else {
|
} else {
|
||||||
line1 += " " +
|
line1 += " " +
|
||||||
|
|
|
@ -320,9 +320,9 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
|
||||||
dataMap.putString("delta", "--");
|
dataMap.putString("delta", "--");
|
||||||
dataMap.putString("avgDelta", "--");
|
dataMap.putString("avgDelta", "--");
|
||||||
} else {
|
} else {
|
||||||
dataMap.putString("slopeArrow", slopeArrow(glucoseStatus.delta));
|
dataMap.putString("slopeArrow", slopeArrow(glucoseStatus.getDelta()));
|
||||||
dataMap.putString("delta", deltastring(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units));
|
dataMap.putString("delta", deltastring(glucoseStatus.getDelta(), glucoseStatus.getDelta() * Constants.MGDL_TO_MMOLL, units));
|
||||||
dataMap.putString("avgDelta", deltastring(glucoseStatus.avgdelta, glucoseStatus.avgdelta * Constants.MGDL_TO_MMOLL, units));
|
dataMap.putString("avgDelta", deltastring(glucoseStatus.getAvgDelta(), glucoseStatus.getAvgDelta() * Constants.MGDL_TO_MMOLL, units));
|
||||||
}
|
}
|
||||||
dataMap.putLong("sgvLevel", sgvLevel);
|
dataMap.putLong("sgvLevel", sgvLevel);
|
||||||
dataMap.putDouble("sgvDouble", lastBG.getValue());
|
dataMap.putDouble("sgvDouble", lastBG.getValue());
|
||||||
|
|
|
@ -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<DataPoint> activitySeries = null;
|
|
||||||
LineGraphSeries<DataPoint> iobSeries = null;
|
|
||||||
List<DataPoint> activityArray = new ArrayList<>();
|
|
||||||
List<DataPoint> 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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<DataPoint> = ArrayList()
|
||||||
|
val iobArray: MutableList<DataPoint> = 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package info.nightscout.androidaps.plugins.iob.iobCobCalculator
|
||||||
|
|
||||||
|
class BasalData {
|
||||||
|
|
||||||
|
var basal = 0.0
|
||||||
|
var tempBasalAbsolute = 0.0
|
||||||
|
var isTempBasalRunning = false
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<BgReading> data = MainApp.getDbHelper().getBgreadingsDataFromTime(fromtime, false);
|
|
||||||
|
|
||||||
synchronized (iobCobCalculatorPlugin.getDataLock()) {
|
|
||||||
|
|
||||||
List<GlucoseValue> 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<Double> now_value_list = new ArrayList<>();
|
|
||||||
ArrayList<Double> last_deltas = new ArrayList<>();
|
|
||||||
ArrayList<Double> short_deltas = new ArrayList<>();
|
|
||||||
ArrayList<Double> 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<Double> array) {
|
|
||||||
double sum = 0d;
|
|
||||||
|
|
||||||
if (array.size() == 0)
|
|
||||||
return 0d;
|
|
||||||
|
|
||||||
for (Double value : array) {
|
|
||||||
sum += value;
|
|
||||||
}
|
|
||||||
return sum / array.size();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<Double>()
|
||||||
|
val lastDeltas = ArrayList<Double>()
|
||||||
|
val shortDeltas = ArrayList<Double>()
|
||||||
|
val longDeltas = ArrayList<Double>()
|
||||||
|
|
||||||
|
// 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>): Double {
|
||||||
|
var sum = 0.0
|
||||||
|
if (array.size == 0) return 0.0
|
||||||
|
for (value in array) {
|
||||||
|
sum += value
|
||||||
|
}
|
||||||
|
return sum / array.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<IobTotal> iobTable = new LongSparseArray<>(); // oldest at index 0
|
|
||||||
private LongSparseArray<IobTotal> absIobTable = new LongSparseArray<>(); // oldest at index 0, absolute insulin in the body
|
|
||||||
private LongSparseArray<AutosensData> autosensDataTable = new LongSparseArray<>(); // oldest at index 0
|
|
||||||
private LongSparseArray<BasalData> basalDataTable = new LongSparseArray<>(); // oldest at index 0
|
|
||||||
|
|
||||||
private volatile List<GlucoseValue> bgReadings = null; // newest at index 0
|
|
||||||
private volatile List<InMemoryGlucoseValue> 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<AutosensData> getAutosensDataTable() {
|
|
||||||
return autosensDataTable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<GlucoseValue> getBgReadings() {
|
|
||||||
return bgReadings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBgReadings(List<GlucoseValue> bgReadings) {
|
|
||||||
this.bgReadings = bgReadings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<InMemoryGlucoseValue> 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<Treatment> 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<Treatment> 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<GlucoseValue> 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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<IobTotal?>() // oldest at index 0
|
||||||
|
private var absIobTable = LongSparseArray<IobTotal?>() // oldest at index 0, absolute insulin in the body
|
||||||
|
private var autosensDataTable = LongSparseArray<AutosensData>() // oldest at index 0
|
||||||
|
private var basalDataTable = LongSparseArray<BasalData>() // oldest at index 0
|
||||||
|
@Volatile var bgReadings: List<GlucoseValue> = listOf() // newest at index 0
|
||||||
|
@Volatile var bucketedData: MutableList<InMemoryGlucoseValue>? = 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<AutosensData> {
|
||||||
|
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<InMemoryGlucoseValue> = 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<IobTotal> {
|
||||||
|
// 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<IobTotal> {
|
||||||
|
// 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<IobTotal>): 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<IobTotal>): 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<Double>, 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<InMemoryGlucoseValue> bucketed_data = iobCobCalculatorPlugin.getBucketedData();
|
|
||||||
LongSparseArray<AutosensData> 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<Treatment> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<InMemoryGlucoseValue> bucketed_data = iobCobCalculatorPlugin.getBucketedData();
|
|
||||||
LongSparseArray<AutosensData> 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<Treatment> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,13 +24,12 @@ import info.nightscout.androidaps.db.ProfileSwitch;
|
||||||
import info.nightscout.androidaps.interfaces.IobCobCalculatorInterface;
|
import info.nightscout.androidaps.interfaces.IobCobCalculatorInterface;
|
||||||
import info.nightscout.androidaps.interfaces.PluginDescription;
|
import info.nightscout.androidaps.interfaces.PluginDescription;
|
||||||
import info.nightscout.androidaps.interfaces.PluginType;
|
import info.nightscout.androidaps.interfaces.PluginType;
|
||||||
|
import info.nightscout.androidaps.interfaces.ProfileFunction;
|
||||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
import info.nightscout.androidaps.logging.AAPSLogger;
|
||||||
import info.nightscout.androidaps.logging.LTag;
|
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.AutosensResult;
|
||||||
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin;
|
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.DateUtil;
|
||||||
import info.nightscout.androidaps.utils.resources.ResourceHelper;
|
import info.nightscout.androidaps.utils.resources.ResourceHelper;
|
||||||
import info.nightscout.androidaps.utils.sharedPreferences.SP;
|
import info.nightscout.androidaps.utils.sharedPreferences.SP;
|
||||||
|
@ -161,7 +160,7 @@ public class SensitivityAAPSPlugin extends AbstractSensitivityPlugin {
|
||||||
|
|
||||||
Arrays.sort(deviations);
|
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 basalOff = percentile * (60.0 / 5.0) / sens;
|
||||||
double ratio = 1 + (basalOff / profile.getMaxDailyBasal());
|
double ratio = 1 + (basalOff / profile.getMaxDailyBasal());
|
||||||
|
|
||||||
|
|
|
@ -207,8 +207,8 @@ public class SensitivityOref1Plugin extends AbstractSensitivityPlugin {
|
||||||
getAapsLogger().debug(LTag.AUTOSENS, "Records: " + index + " " + pastSensitivity);
|
getAapsLogger().debug(LTag.AUTOSENS, "Records: " + index + " " + pastSensitivity);
|
||||||
|
|
||||||
Arrays.sort(deviations);
|
Arrays.sort(deviations);
|
||||||
double pSensitive = IobCobCalculatorPlugin.percentile(deviations, 0.50);
|
double pSensitive = IobCobCalculatorPlugin.Companion.percentile(deviations, 0.50);
|
||||||
double pResistant = IobCobCalculatorPlugin.percentile(deviations, 0.50);
|
double pResistant = IobCobCalculatorPlugin.Companion.percentile(deviations, 0.50);
|
||||||
|
|
||||||
double basalOff = 0;
|
double basalOff = 0;
|
||||||
|
|
||||||
|
|
|
@ -8,13 +8,13 @@ import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@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 devBranch = false
|
||||||
private var engineeringMode = false
|
private var engineeringMode = false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val extFilesDir = LoggerUtils.getLogDirectory()
|
val extFilesDir = loggerUtils.logDirectory
|
||||||
val engineeringModeSemaphore = File(extFilesDir, "engineering__mode")
|
val engineeringModeSemaphore = File(extFilesDir, "engineering__mode")
|
||||||
|
|
||||||
engineeringMode = engineeringModeSemaphore.exists() && engineeringModeSemaphore.isFile
|
engineeringMode = engineeringModeSemaphore.exists() && engineeringModeSemaphore.isFile
|
||||||
|
|
|
@ -194,7 +194,7 @@ class BolusWizard @Inject constructor(
|
||||||
glucoseStatus = GlucoseStatus(injector).glucoseStatusData
|
glucoseStatus = GlucoseStatus(injector).glucoseStatusData
|
||||||
glucoseStatus?.let {
|
glucoseStatus?.let {
|
||||||
if (useTrend) {
|
if (useTrend) {
|
||||||
trend = it.short_avgdelta
|
trend = it.shortAvgDelta
|
||||||
insulinFromTrend = Profile.fromMgdlToUnits(trend, profileFunction.getUnits()) * 3 / sens
|
insulinFromTrend = Profile.fromMgdlToUnits(trend, profileFunction.getUnits()) * 3 / sens
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,9 +112,9 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec
|
||||||
var trend = false
|
var trend = false
|
||||||
if (useTrend() == YES) {
|
if (useTrend() == YES) {
|
||||||
trend = true
|
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
|
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
|
trend = true
|
||||||
}
|
}
|
||||||
val percentage = sp.getDouble(R.string.key_boluswizard_percentage, 100.0)
|
val percentage = sp.getDouble(R.string.key_boluswizard_percentage, 100.0)
|
||||||
|
|
|
@ -23,7 +23,7 @@ import org.powermock.core.classloader.annotations.PrepareForTest
|
||||||
import org.powermock.modules.junit4.PowerMockRunner
|
import org.powermock.modules.junit4.PowerMockRunner
|
||||||
|
|
||||||
@RunWith(PowerMockRunner::class)
|
@RunWith(PowerMockRunner::class)
|
||||||
@PrepareForTest(Profile::class)
|
@PrepareForTest(Profile::class, IobCobCalculatorPlugin::class)
|
||||||
class QuickWizardTest : TestBase() {
|
class QuickWizardTest : TestBase() {
|
||||||
|
|
||||||
@Mock lateinit var sp: SP
|
@Mock lateinit var sp: SP
|
||||||
|
@ -68,7 +68,7 @@ class QuickWizardTest : TestBase() {
|
||||||
|
|
||||||
@Test fun test() {
|
@Test fun test() {
|
||||||
quickWizard.setData(array)
|
quickWizard.setData(array)
|
||||||
Assert.assertEquals("Lunch", quickWizard.get(1).buttonText())
|
Assert.assertEquals("Lunch", quickWizard[1].buttonText())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun active() {
|
@Test fun active() {
|
||||||
|
|
|
@ -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.ObjectivesPlugin
|
||||||
import info.nightscout.androidaps.plugins.constraints.objectives.objectives.Objective
|
import info.nightscout.androidaps.plugins.constraints.objectives.objectives.Objective
|
||||||
import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin
|
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.NSUpload
|
||||||
import info.nightscout.androidaps.plugins.general.nsclient.UploadQueue
|
import info.nightscout.androidaps.plugins.general.nsclient.UploadQueue
|
||||||
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
|
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
|
||||||
|
@ -47,7 +48,11 @@ import java.util.*
|
||||||
* Created by mike on 18.03.2018.
|
* Created by mike on 18.03.2018.
|
||||||
*/
|
*/
|
||||||
@RunWith(PowerMockRunner::class)
|
@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() {
|
class ConstraintsCheckerTest : TestBaseWithProfile() {
|
||||||
|
|
||||||
@Mock lateinit var activePlugin: ActivePluginProvider
|
@Mock lateinit var activePlugin: ActivePluginProvider
|
||||||
|
@ -63,8 +68,8 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
|
||||||
@Mock lateinit var nsUpload: NSUpload
|
@Mock lateinit var nsUpload: NSUpload
|
||||||
@Mock lateinit var uploadQueue: UploadQueue
|
@Mock lateinit var uploadQueue: UploadQueue
|
||||||
@Mock lateinit var uel: UserEntryLogger
|
@Mock lateinit var uel: UserEntryLogger
|
||||||
|
@Mock lateinit var loggerUtils: LoggerUtils
|
||||||
|
|
||||||
private var buildHelper = BuildHelper(Config())
|
|
||||||
lateinit var danaPump: DanaPump
|
lateinit var danaPump: DanaPump
|
||||||
|
|
||||||
lateinit var constraintChecker: ConstraintChecker
|
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)
|
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)
|
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)
|
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<PluginBase>()
|
val constraintsPluginsList = ArrayList<PluginBase>()
|
||||||
constraintsPluginsList.add(safetyPlugin)
|
constraintsPluginsList.add(safetyPlugin)
|
||||||
constraintsPluginsList.add(objectivesPlugin)
|
constraintsPluginsList.add(objectivesPlugin)
|
||||||
|
|
|
@ -33,7 +33,9 @@ import org.powermock.core.classloader.annotations.PrepareForTest
|
||||||
import org.powermock.modules.junit4.PowerMockRunner
|
import org.powermock.modules.junit4.PowerMockRunner
|
||||||
|
|
||||||
@RunWith(PowerMockRunner::class)
|
@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() {
|
class LoopPluginTest : TestBase() {
|
||||||
|
|
||||||
@Mock lateinit var sp: SP
|
@Mock lateinit var sp: SP
|
||||||
|
|
|
@ -19,7 +19,7 @@ import org.junit.Before
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.powermock.core.classloader.annotations.PrepareForTest
|
import org.powermock.core.classloader.annotations.PrepareForTest
|
||||||
|
|
||||||
@PrepareForTest(LastLocationDataContainer::class, AutomationPlugin::class)
|
@PrepareForTest(LastLocationDataContainer::class, AutomationPlugin::class, IobCobCalculatorPlugin::class)
|
||||||
open class TriggerTestBase : TestBaseWithProfile() {
|
open class TriggerTestBase : TestBaseWithProfile() {
|
||||||
|
|
||||||
@Mock lateinit var sp: SP
|
@Mock lateinit var sp: SP
|
||||||
|
|
|
@ -13,12 +13,13 @@ import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito.`when`
|
||||||
import org.powermock.core.classloader.annotations.PrepareForTest
|
import org.powermock.core.classloader.annotations.PrepareForTest
|
||||||
import org.powermock.modules.junit4.PowerMockRunner
|
import org.powermock.modules.junit4.PowerMockRunner
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@RunWith(PowerMockRunner::class)
|
@RunWith(PowerMockRunner::class)
|
||||||
@PrepareForTest(NSSettingsStatus::class, BuildHelper::class)
|
@PrepareForTest(NSSettingsStatus::class, BuildHelper::class, LoggerUtils::class)
|
||||||
class MaintenancePluginTest : TestBase() {
|
class MaintenancePluginTest : TestBase() {
|
||||||
|
|
||||||
@Mock lateinit var injector: HasAndroidInjector
|
@Mock lateinit var injector: HasAndroidInjector
|
||||||
|
@ -27,28 +28,29 @@ class MaintenancePluginTest : TestBase() {
|
||||||
@Mock lateinit var sp: SP
|
@Mock lateinit var sp: SP
|
||||||
@Mock lateinit var nsSettingsStatus: NSSettingsStatus
|
@Mock lateinit var nsSettingsStatus: NSSettingsStatus
|
||||||
@Mock lateinit var buildHelper: BuildHelper
|
@Mock lateinit var buildHelper: BuildHelper
|
||||||
|
@Mock lateinit var loggerUtils: LoggerUtils
|
||||||
|
|
||||||
lateinit var sut: MaintenancePlugin
|
lateinit var sut: MaintenancePlugin
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun mock() {
|
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() {
|
@Test fun logFilesTest() {
|
||||||
val logDirectory = "src/test/res/logger"
|
var logs = sut.getLogFiles(2)
|
||||||
var logs = sut.getLogFiles(logDirectory, 2)
|
|
||||||
Assert.assertEquals(2, logs.size)
|
Assert.assertEquals(2, logs.size)
|
||||||
Assert.assertEquals("AndroidAPS.log", logs[0].name)
|
Assert.assertEquals("AndroidAPS.log", logs[0].name)
|
||||||
Assert.assertEquals("AndroidAPS.2018-01-03_01-01-00.1.zip", logs[1].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)
|
Assert.assertEquals(4, logs.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun zipLogsTest() {
|
fun zipLogsTest() {
|
||||||
val logDirectory = "src/test/res/logger"
|
val logs = sut.getLogFiles(2)
|
||||||
val logs = sut.getLogFiles(logDirectory, 2)
|
|
||||||
val name = "AndroidAPS.log.zip"
|
val name = "AndroidAPS.log.zip"
|
||||||
var zipFile = File("build/$name")
|
var zipFile = File("build/$name")
|
||||||
zipFile = sut.zipLogs(zipFile, logs)
|
zipFile = sut.zipLogs(zipFile, logs)
|
||||||
|
|
|
@ -52,9 +52,9 @@ class GlucoseStatusTest : TestBase() {
|
||||||
val glucoseStatus = GlucoseStatus(injector).glucoseStatusData!!
|
val glucoseStatus = GlucoseStatus(injector).glucoseStatusData!!
|
||||||
Assert.assertEquals(214.0, glucoseStatus.glucose, 0.001)
|
Assert.assertEquals(214.0, glucoseStatus.glucose, 0.001)
|
||||||
Assert.assertEquals(-2.0, glucoseStatus.delta, 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.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.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.0, glucoseStatus.longAvgDelta, 0.001) // -2 -2 -2 -2
|
||||||
Assert.assertEquals(1514766900000L, glucoseStatus.date) // latest date
|
Assert.assertEquals(1514766900000L, glucoseStatus.date) // latest date
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,9 +63,9 @@ class GlucoseStatusTest : TestBase() {
|
||||||
val glucoseStatus: GlucoseStatus = GlucoseStatus(injector).glucoseStatusData!!
|
val glucoseStatus: GlucoseStatus = GlucoseStatus(injector).glucoseStatusData!!
|
||||||
Assert.assertEquals(215.0, glucoseStatus.glucose, 0.001) // (214+216) / 2
|
Assert.assertEquals(215.0, glucoseStatus.glucose, 0.001) // (214+216) / 2
|
||||||
Assert.assertEquals(-1.0, glucoseStatus.delta, 0.001)
|
Assert.assertEquals(-1.0, glucoseStatus.delta, 0.001)
|
||||||
Assert.assertEquals(-1.0, glucoseStatus.short_avgdelta, 0.001)
|
Assert.assertEquals(-1.0, glucoseStatus.shortAvgDelta, 0.001)
|
||||||
Assert.assertEquals(-1.0, glucoseStatus.avgdelta, 0.001)
|
Assert.assertEquals(-1.0, glucoseStatus.avgDelta, 0.001)
|
||||||
Assert.assertEquals(0.0, glucoseStatus.long_avgdelta, 0.001)
|
Assert.assertEquals(0.0, glucoseStatus.longAvgDelta, 0.001)
|
||||||
Assert.assertEquals(1514766900000L, glucoseStatus.date) // latest date, even when averaging
|
Assert.assertEquals(1514766900000L, glucoseStatus.date) // latest date, even when averaging
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,9 +74,9 @@ class GlucoseStatusTest : TestBase() {
|
||||||
val glucoseStatus: GlucoseStatus = GlucoseStatus(injector).glucoseStatusData!!
|
val glucoseStatus: GlucoseStatus = GlucoseStatus(injector).glucoseStatusData!!
|
||||||
Assert.assertEquals(214.0, glucoseStatus.glucose, 0.001)
|
Assert.assertEquals(214.0, glucoseStatus.glucose, 0.001)
|
||||||
Assert.assertEquals(0.0, glucoseStatus.delta, 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.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.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.longAvgDelta, 0.001) // -2 -2 -2 -2
|
||||||
Assert.assertEquals(1514766900000L, glucoseStatus.date) // latest date
|
Assert.assertEquals(1514766900000L, glucoseStatus.date) // latest date
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,9 +107,9 @@ class GlucoseStatusTest : TestBase() {
|
||||||
val glucoseStatus: GlucoseStatus = GlucoseStatus(injector).glucoseStatusData!!
|
val glucoseStatus: GlucoseStatus = GlucoseStatus(injector).glucoseStatusData!!
|
||||||
Assert.assertEquals(100.0, glucoseStatus.glucose, 0.001) //
|
Assert.assertEquals(100.0, glucoseStatus.glucose, 0.001) //
|
||||||
Assert.assertEquals(-10.0, glucoseStatus.delta, 0.001)
|
Assert.assertEquals(-10.0, glucoseStatus.delta, 0.001)
|
||||||
Assert.assertEquals(-10.0, glucoseStatus.short_avgdelta, 0.001)
|
Assert.assertEquals(-10.0, glucoseStatus.shortAvgDelta, 0.001)
|
||||||
Assert.assertEquals(-10.0, glucoseStatus.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.longAvgDelta, 0.001)
|
||||||
Assert.assertEquals(1514766900000L, glucoseStatus.date) // latest date
|
Assert.assertEquals(1514766900000L, glucoseStatus.date) // latest date
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,6 @@ import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin
|
||||||
import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin
|
import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin
|
||||||
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
|
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
|
||||||
import info.nightscout.androidaps.utils.DateUtil
|
import info.nightscout.androidaps.utils.DateUtil
|
||||||
import info.nightscout.androidaps.utils.DefaultValueHelper
|
|
||||||
import info.nightscout.androidaps.utils.FabricPrivacy
|
import info.nightscout.androidaps.utils.FabricPrivacy
|
||||||
import info.nightscout.androidaps.utils.T
|
import info.nightscout.androidaps.utils.T
|
||||||
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||||
|
@ -40,7 +39,6 @@ class IobCobCalculatorPluginTest : TestBase() {
|
||||||
@Mock lateinit var sensitivityOref1Plugin: SensitivityOref1Plugin
|
@Mock lateinit var sensitivityOref1Plugin: SensitivityOref1Plugin
|
||||||
@Mock lateinit var sensitivityAAPSPlugin: SensitivityAAPSPlugin
|
@Mock lateinit var sensitivityAAPSPlugin: SensitivityAAPSPlugin
|
||||||
@Mock lateinit var sensitivityWeightedAveragePlugin: SensitivityWeightedAveragePlugin
|
@Mock lateinit var sensitivityWeightedAveragePlugin: SensitivityWeightedAveragePlugin
|
||||||
@Mock lateinit var defaultValueHelper: DefaultValueHelper
|
|
||||||
@Mock lateinit var fabricPrivacy: FabricPrivacy
|
@Mock lateinit var fabricPrivacy: FabricPrivacy
|
||||||
@Mock lateinit var dateUtil: DateUtil
|
@Mock lateinit var dateUtil: DateUtil
|
||||||
@Mock lateinit var repository: AppRepository
|
@Mock lateinit var repository: AppRepository
|
||||||
|
@ -137,9 +135,9 @@ class IobCobCalculatorPluginTest : TestBase() {
|
||||||
iobCobCalculatorPlugin.bgReadings = bgReadingList
|
iobCobCalculatorPlugin.bgReadings = bgReadingList
|
||||||
Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData)
|
Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData)
|
||||||
iobCobCalculatorPlugin.createBucketedData()
|
iobCobCalculatorPlugin.createBucketedData()
|
||||||
Assert.assertEquals(bgReadingList[0].timestamp, iobCobCalculatorPlugin.bucketedData[0].timestamp)
|
Assert.assertEquals(bgReadingList[0].timestamp, iobCobCalculatorPlugin.bucketedData!![0].timestamp)
|
||||||
Assert.assertEquals(bgReadingList[3].timestamp, iobCobCalculatorPlugin.bucketedData[3].timestamp)
|
Assert.assertEquals(bgReadingList[3].timestamp, iobCobCalculatorPlugin.bucketedData!![3].timestamp)
|
||||||
Assert.assertEquals(bgReadingList.size.toLong(), iobCobCalculatorPlugin.bucketedData.size.toLong())
|
Assert.assertEquals(bgReadingList.size.toLong(), iobCobCalculatorPlugin.bucketedData!!.size.toLong())
|
||||||
|
|
||||||
// Missing value should be replaced
|
// Missing value should be replaced
|
||||||
bgReadingList.clear()
|
bgReadingList.clear()
|
||||||
|
@ -149,9 +147,9 @@ class IobCobCalculatorPluginTest : TestBase() {
|
||||||
iobCobCalculatorPlugin.bgReadings = bgReadingList
|
iobCobCalculatorPlugin.bgReadings = bgReadingList
|
||||||
Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData)
|
Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData)
|
||||||
iobCobCalculatorPlugin.createBucketedData()
|
iobCobCalculatorPlugin.createBucketedData()
|
||||||
Assert.assertEquals(bgReadingList[0].timestamp, iobCobCalculatorPlugin.bucketedData[0].timestamp)
|
Assert.assertEquals(bgReadingList[0].timestamp, iobCobCalculatorPlugin.bucketedData!![0].timestamp)
|
||||||
Assert.assertEquals(bgReadingList[2].timestamp, iobCobCalculatorPlugin.bucketedData[3].timestamp)
|
Assert.assertEquals(bgReadingList[2].timestamp, iobCobCalculatorPlugin.bucketedData!![3].timestamp)
|
||||||
Assert.assertEquals(bgReadingList.size + 1.toLong(), iobCobCalculatorPlugin.bucketedData.size.toLong())
|
Assert.assertEquals(bgReadingList.size + 1.toLong(), iobCobCalculatorPlugin.bucketedData!!.size.toLong())
|
||||||
|
|
||||||
// drift should be cleared
|
// drift should be cleared
|
||||||
bgReadingList.clear()
|
bgReadingList.clear()
|
||||||
|
@ -163,11 +161,11 @@ class IobCobCalculatorPluginTest : TestBase() {
|
||||||
iobCobCalculatorPlugin.bgReadings = bgReadingList
|
iobCobCalculatorPlugin.bgReadings = bgReadingList
|
||||||
Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData)
|
Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData)
|
||||||
iobCobCalculatorPlugin.createBucketedData()
|
iobCobCalculatorPlugin.createBucketedData()
|
||||||
Assert.assertEquals(T.mins(20).msecs(), iobCobCalculatorPlugin.bucketedData[0].timestamp)
|
Assert.assertEquals(T.mins(20).msecs(), iobCobCalculatorPlugin.bucketedData!![0].timestamp)
|
||||||
Assert.assertEquals(T.mins(15).msecs(), iobCobCalculatorPlugin.bucketedData[1].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(10).msecs(), iobCobCalculatorPlugin.bucketedData!![2].timestamp)
|
||||||
Assert.assertEquals(T.mins(5).msecs(), iobCobCalculatorPlugin.bucketedData[3].timestamp)
|
Assert.assertEquals(T.mins(5).msecs(), iobCobCalculatorPlugin.bucketedData!![3].timestamp)
|
||||||
Assert.assertEquals(bgReadingList.size.toLong(), iobCobCalculatorPlugin.bucketedData.size.toLong())
|
Assert.assertEquals(bgReadingList.size.toLong(), iobCobCalculatorPlugin.bucketedData!!.size.toLong())
|
||||||
|
|
||||||
// bucketed data should return null if not enough bg data
|
// bucketed data should return null if not enough bg data
|
||||||
bgReadingList.clear()
|
bgReadingList.clear()
|
||||||
|
@ -186,13 +184,13 @@ class IobCobCalculatorPluginTest : TestBase() {
|
||||||
iobCobCalculatorPlugin.bgReadings = bgReadingList
|
iobCobCalculatorPlugin.bgReadings = bgReadingList
|
||||||
Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData)
|
Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData)
|
||||||
iobCobCalculatorPlugin.createBucketedData()
|
iobCobCalculatorPlugin.createBucketedData()
|
||||||
Assert.assertEquals(T.mins(50).msecs(), iobCobCalculatorPlugin.bucketedData[0].timestamp)
|
Assert.assertEquals(T.mins(50).msecs(), iobCobCalculatorPlugin.bucketedData!![0].timestamp)
|
||||||
Assert.assertEquals(T.mins(20).msecs(), iobCobCalculatorPlugin.bucketedData[6].timestamp)
|
Assert.assertEquals(T.mins(20).msecs(), iobCobCalculatorPlugin.bucketedData!![6].timestamp)
|
||||||
Assert.assertEquals(7, iobCobCalculatorPlugin.bucketedData.size.toLong())
|
Assert.assertEquals(7, iobCobCalculatorPlugin.bucketedData!!.size.toLong())
|
||||||
Assert.assertEquals(100.0, iobCobCalculatorPlugin.bucketedData[0].value, 1.0)
|
Assert.assertEquals(100.0, iobCobCalculatorPlugin.bucketedData!![0].value, 1.0)
|
||||||
Assert.assertEquals(90.0, iobCobCalculatorPlugin.bucketedData[1].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(50.0, iobCobCalculatorPlugin.bucketedData!![5].value, 1.0)
|
||||||
Assert.assertEquals(40.0, iobCobCalculatorPlugin.bucketedData[6].value, 1.0)
|
Assert.assertEquals(40.0, iobCobCalculatorPlugin.bucketedData!![6].value, 1.0)
|
||||||
|
|
||||||
// non 5min data should be reconstructed
|
// non 5min data should be reconstructed
|
||||||
bgReadingList.clear()
|
bgReadingList.clear()
|
||||||
|
@ -202,16 +200,16 @@ class IobCobCalculatorPluginTest : TestBase() {
|
||||||
iobCobCalculatorPlugin.bgReadings = bgReadingList
|
iobCobCalculatorPlugin.bgReadings = bgReadingList
|
||||||
Assert.assertEquals(false, iobCobCalculatorPlugin.isAbout5minData)
|
Assert.assertEquals(false, iobCobCalculatorPlugin.isAbout5minData)
|
||||||
iobCobCalculatorPlugin.createBucketedData()
|
iobCobCalculatorPlugin.createBucketedData()
|
||||||
Assert.assertEquals(T.mins(50).msecs(), iobCobCalculatorPlugin.bucketedData[0].timestamp)
|
Assert.assertEquals(T.mins(50).msecs(), iobCobCalculatorPlugin.bucketedData!![0].timestamp)
|
||||||
Assert.assertEquals(T.mins(20).msecs(), iobCobCalculatorPlugin.bucketedData[6].timestamp)
|
Assert.assertEquals(T.mins(20).msecs(), iobCobCalculatorPlugin.bucketedData!![6].timestamp)
|
||||||
Assert.assertEquals(7, iobCobCalculatorPlugin.bucketedData.size.toLong())
|
Assert.assertEquals(7, iobCobCalculatorPlugin.bucketedData!!.size.toLong())
|
||||||
Assert.assertEquals(100.0, iobCobCalculatorPlugin.bucketedData[0].value, 1.0)
|
Assert.assertEquals(100.0, iobCobCalculatorPlugin.bucketedData!![0].value, 1.0)
|
||||||
Assert.assertEquals(90.0, iobCobCalculatorPlugin.bucketedData[1].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(50.0, iobCobCalculatorPlugin.bucketedData!![5].value, 1.0)
|
||||||
Assert.assertEquals(40.0, iobCobCalculatorPlugin.bucketedData[6].value, 1.0)
|
Assert.assertEquals(40.0, iobCobCalculatorPlugin.bucketedData!![6].value, 1.0)
|
||||||
|
|
||||||
//bucketed data should be null if no bg data available
|
//bucketed data should be null if no bg data available
|
||||||
iobCobCalculatorPlugin.bgReadings = null
|
iobCobCalculatorPlugin.bgReadings = ArrayList()
|
||||||
iobCobCalculatorPlugin.createBucketedData()
|
iobCobCalculatorPlugin.createBucketedData()
|
||||||
Assert.assertEquals(null, iobCobCalculatorPlugin.bucketedData)
|
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: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))
|
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.bgReadings = bgReadingList
|
||||||
iobCobCalculatorPlugin.referenceTime = null
|
iobCobCalculatorPlugin.referenceTime = -1
|
||||||
Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData)
|
Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData)
|
||||||
iobCobCalculatorPlugin.createBucketedData()
|
iobCobCalculatorPlugin.createBucketedData()
|
||||||
Assert.assertEquals(DateUtil.fromISODateString("2018-09-05T13:34:57Z").time, iobCobCalculatorPlugin.bucketedData[0].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)
|
Assert.assertEquals(DateUtil.fromISODateString("2018-09-05T03:44:57Z").time, iobCobCalculatorPlugin.bucketedData!![iobCobCalculatorPlugin.bucketedData!!.size - 1].timestamp)
|
||||||
|
|
||||||
// 5min 4sec data
|
// 5min 4sec data
|
||||||
bgReadingList.clear()
|
bgReadingList.clear()
|
||||||
|
|
|
@ -11,6 +11,7 @@ import info.nightscout.androidaps.interfaces.ActivePluginProvider
|
||||||
import info.nightscout.androidaps.interfaces.Constraint
|
import info.nightscout.androidaps.interfaces.Constraint
|
||||||
import info.nightscout.androidaps.interfaces.PumpDescription
|
import info.nightscout.androidaps.interfaces.PumpDescription
|
||||||
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
|
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.pump.virtual.VirtualPumpPlugin
|
||||||
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
|
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
|
||||||
import info.nightscout.androidaps.queue.commands.Command
|
import info.nightscout.androidaps.queue.commands.Command
|
||||||
|
@ -30,7 +31,9 @@ import org.powermock.modules.junit4.PowerMockRunner
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@RunWith(PowerMockRunner::class)
|
@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() {
|
class CommandQueueTest : TestBaseWithProfile() {
|
||||||
|
|
||||||
@Mock lateinit var constraintChecker: ConstraintChecker
|
@Mock lateinit var constraintChecker: ConstraintChecker
|
||||||
|
@ -39,8 +42,7 @@ class CommandQueueTest : TestBaseWithProfile() {
|
||||||
@Mock lateinit var context: Context
|
@Mock lateinit var context: Context
|
||||||
@Mock lateinit var virtualPumpPlugin: VirtualPumpPlugin
|
@Mock lateinit var virtualPumpPlugin: VirtualPumpPlugin
|
||||||
@Mock lateinit var sp: SP
|
@Mock lateinit var sp: SP
|
||||||
|
@Mock lateinit var loggerUtils: LoggerUtils
|
||||||
private val buildHelper = BuildHelper(Config())
|
|
||||||
|
|
||||||
val injector = HasAndroidInjector {
|
val injector = HasAndroidInjector {
|
||||||
AndroidInjector {
|
AndroidInjector {
|
||||||
|
@ -55,7 +57,7 @@ class CommandQueueTest : TestBaseWithProfile() {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun prepare() {
|
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()
|
val pumpDescription = PumpDescription()
|
||||||
pumpDescription.basalMinimumRate = 0.1
|
pumpDescription.basalMinimumRate = 0.1
|
||||||
|
@ -65,7 +67,7 @@ class CommandQueueTest : TestBaseWithProfile() {
|
||||||
`when`(virtualPumpPlugin.pumpDescription).thenReturn(pumpDescription)
|
`when`(virtualPumpPlugin.pumpDescription).thenReturn(pumpDescription)
|
||||||
`when`(virtualPumpPlugin.isThisProfileSet(anyObject())).thenReturn(false)
|
`when`(virtualPumpPlugin.isThisProfileSet(anyObject())).thenReturn(false)
|
||||||
`when`(activePlugin.activeTreatments).thenReturn(treatmentsPlugin)
|
`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)
|
`when`(profileFunction.getProfile()).thenReturn(validProfile)
|
||||||
|
|
||||||
val bolusConstraint = Constraint(0.0)
|
val bolusConstraint = Constraint(0.0)
|
||||||
|
|
|
@ -31,10 +31,10 @@ import org.powermock.core.classloader.annotations.PrepareForTest
|
||||||
import org.powermock.modules.junit4.PowerMockRunner
|
import org.powermock.modules.junit4.PowerMockRunner
|
||||||
|
|
||||||
@RunWith(PowerMockRunner::class)
|
@RunWith(PowerMockRunner::class)
|
||||||
@PrepareForTest(ConstraintChecker::class, VirtualPumpPlugin::class)
|
@PrepareForTest(ConstraintChecker::class, VirtualPumpPlugin::class, IobCobCalculatorPlugin::class)
|
||||||
class BolusWizardTest : TestBase() {
|
class BolusWizardTest : TestBase() {
|
||||||
|
|
||||||
private val PUMP_BOLUS_STEP = 0.1
|
private val pumpBolusStep = 0.1
|
||||||
|
|
||||||
@Mock lateinit var resourceHelper: ResourceHelper
|
@Mock lateinit var resourceHelper: ResourceHelper
|
||||||
@Mock lateinit var profileFunction: ProfileFunction
|
@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 {
|
private fun setupProfile(targetLow: Double, targetHigh: Double, insulinSensitivityFactor: Double, insulinToCarbRatio: Double): Profile {
|
||||||
val profile = Mockito.mock(Profile::class.java)
|
val profile = Mockito.mock(Profile::class.java)
|
||||||
`when`(profile.targetLowMgdl).thenReturn(targetLow)
|
`when`(profile.targetLowMgdl).thenReturn(targetLow)
|
||||||
|
@ -81,7 +82,7 @@ class BolusWizardTest : TestBase() {
|
||||||
`when`(treatmentsPlugin.lastCalculationTempBasals).thenReturn(IobTotal(System.currentTimeMillis()))
|
`when`(treatmentsPlugin.lastCalculationTempBasals).thenReturn(IobTotal(System.currentTimeMillis()))
|
||||||
`when`(activePlugin.activePump).thenReturn(virtualPumpPlugin)
|
`when`(activePlugin.activePump).thenReturn(virtualPumpPlugin)
|
||||||
val pumpDescription = PumpDescription()
|
val pumpDescription = PumpDescription()
|
||||||
pumpDescription.bolusStep = PUMP_BOLUS_STEP
|
pumpDescription.bolusStep = pumpBolusStep
|
||||||
`when`(virtualPumpPlugin.pumpDescription).thenReturn(pumpDescription)
|
`when`(virtualPumpPlugin.pumpDescription).thenReturn(pumpDescription)
|
||||||
|
|
||||||
Mockito.doAnswer { invocation: InvocationOnMock ->
|
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 */
|
/** Should calculate the same bolus when different blood glucose but both in target range */
|
||||||
fun shouldCalculateTheSameBolusWhenBGsInRange() {
|
fun shouldCalculateTheSameBolusWhenBGsInRange() {
|
||||||
val profile = setupProfile(4.0, 8.0, 20.0, 12.0)
|
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
|
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
|
val bolusForBg54 = bw.calculatedTotalInsulin
|
||||||
Assert.assertEquals(bolusForBg42, bolusForBg54, 0.01)
|
Assert.assertEquals(bolusForBg42, bolusForBg54, 0.01)
|
||||||
}
|
}
|
||||||
|
@ -104,9 +105,9 @@ class BolusWizardTest : TestBase() {
|
||||||
@Test
|
@Test
|
||||||
fun shouldCalculateHigherBolusWhenHighBG() {
|
fun shouldCalculateHigherBolusWhenHighBG() {
|
||||||
val profile = setupProfile(4.0, 8.0, 20.0, 12.0)
|
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
|
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
|
val bolusForBgInRange = bw.calculatedTotalInsulin
|
||||||
Assert.assertTrue(bolusForHighBg > bolusForBgInRange)
|
Assert.assertTrue(bolusForHighBg > bolusForBgInRange)
|
||||||
}
|
}
|
||||||
|
@ -114,9 +115,9 @@ class BolusWizardTest : TestBase() {
|
||||||
@Test
|
@Test
|
||||||
fun shouldCalculateLowerBolusWhenLowBG() {
|
fun shouldCalculateLowerBolusWhenLowBG() {
|
||||||
val profile = setupProfile(4.0, 8.0, 20.0, 12.0)
|
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
|
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
|
val bolusForBgInRange = bw.calculatedTotalInsulin
|
||||||
Assert.assertTrue(bolusForLowBg < bolusForBgInRange)
|
Assert.assertTrue(bolusForLowBg < bolusForBgInRange)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ buildscript {
|
||||||
dagger_version = '2.32'
|
dagger_version = '2.32'
|
||||||
coroutinesVersion = '1.4.1'
|
coroutinesVersion = '1.4.1'
|
||||||
activityVersion = '1.2.0'
|
activityVersion = '1.2.0'
|
||||||
fragmentktx_version = '1.3.0-rc02'
|
fragmentktx_version = '1.3.0'
|
||||||
ormLiteVersion = '4.46'
|
ormLiteVersion = '4.46'
|
||||||
nav_version = '2.3.3'
|
nav_version = '2.3.3'
|
||||||
appcompat_version = '1.2.0'
|
appcompat_version = '1.2.0'
|
||||||
|
|
|
@ -46,9 +46,9 @@ public class Constants {
|
||||||
public static final int defaultEatingSoonTTDuration = 45; // min
|
public static final int defaultEatingSoonTTDuration = 45; // min
|
||||||
public static final double defaultEatingSoonTTmgdl = 90d;
|
public static final double defaultEatingSoonTTmgdl = 90d;
|
||||||
public static final double defaultEatingSoonTTmmol = 5d;
|
public static final double defaultEatingSoonTTmmol = 5d;
|
||||||
public static final int defaultHypoTTDuration = 30; // min
|
public static final int defaultHypoTTDuration = 60; // min
|
||||||
public static final double defaultHypoTTmgdl = 120d;
|
public static final double defaultHypoTTmgdl = 160d;
|
||||||
public static final double defaultHypoTTmmol = 6.5d;
|
public static final double defaultHypoTTmmol = 8d;
|
||||||
|
|
||||||
public static final double MIN_TT_MGDL = 72d;
|
public static final double MIN_TT_MGDL = 72d;
|
||||||
public static final double MAX_TT_MGDL = 180d;
|
public static final double MAX_TT_MGDL = 180d;
|
||||||
|
|
|
@ -7,78 +7,20 @@ import info.nightscout.androidaps.data.Profile
|
||||||
*/
|
*/
|
||||||
interface ConstraintsInterface {
|
interface ConstraintsInterface {
|
||||||
|
|
||||||
@JvmDefault
|
@JvmDefault fun isLoopInvocationAllowed(value: Constraint<Boolean>): Constraint<Boolean> = value
|
||||||
fun isLoopInvocationAllowed(value: Constraint<Boolean>): Constraint<Boolean> {
|
@JvmDefault fun isClosedLoopAllowed(value: Constraint<Boolean>): Constraint<Boolean> = value
|
||||||
return value
|
@JvmDefault fun isLgsAllowed(value: Constraint<Boolean>): Constraint<Boolean> = value
|
||||||
}
|
@JvmDefault fun isAutosensModeEnabled(value: Constraint<Boolean>): Constraint<Boolean> = value
|
||||||
|
@JvmDefault fun isAMAModeEnabled(value: Constraint<Boolean>): Constraint<Boolean> = value
|
||||||
@JvmDefault
|
@JvmDefault fun isSMBModeEnabled(value: Constraint<Boolean>): Constraint<Boolean> = value
|
||||||
fun isClosedLoopAllowed(value: Constraint<Boolean>): Constraint<Boolean> {
|
@JvmDefault fun isUAMEnabled(value: Constraint<Boolean>): Constraint<Boolean> = value
|
||||||
return value
|
@JvmDefault fun isAdvancedFilteringEnabled(value: Constraint<Boolean>): Constraint<Boolean> = value
|
||||||
}
|
@JvmDefault fun isSuperBolusEnabled(value: Constraint<Boolean>): Constraint<Boolean> = value
|
||||||
|
@JvmDefault fun applyBasalConstraints(absoluteRate: Constraint<Double>, profile: Profile): Constraint<Double> = absoluteRate
|
||||||
@JvmDefault
|
@JvmDefault fun applyBasalPercentConstraints(percentRate: Constraint<Int>, profile: Profile): Constraint<Int> = percentRate
|
||||||
fun isAutosensModeEnabled(value: Constraint<Boolean>): Constraint<Boolean> {
|
@JvmDefault fun applyBolusConstraints(insulin: Constraint<Double>): Constraint<Double> = insulin
|
||||||
return value
|
@JvmDefault fun applyExtendedBolusConstraints(insulin: Constraint<Double>): Constraint<Double> = insulin
|
||||||
}
|
@JvmDefault fun applyCarbsConstraints(carbs: Constraint<Int>): Constraint<Int> = carbs
|
||||||
|
@JvmDefault fun applyMaxIOBConstraints(maxIob: Constraint<Double>): Constraint<Double> = maxIob
|
||||||
@JvmDefault
|
@JvmDefault fun isAutomationEnabled(value: Constraint<Boolean>): Constraint<Boolean> = value
|
||||||
fun isAMAModeEnabled(value: Constraint<Boolean>): Constraint<Boolean> {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmDefault
|
|
||||||
fun isSMBModeEnabled(value: Constraint<Boolean>): Constraint<Boolean> {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmDefault
|
|
||||||
fun isUAMEnabled(value: Constraint<Boolean>): Constraint<Boolean> {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmDefault
|
|
||||||
fun isAdvancedFilteringEnabled(value: Constraint<Boolean>): Constraint<Boolean> {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmDefault
|
|
||||||
fun isSuperBolusEnabled(value: Constraint<Boolean>): Constraint<Boolean> {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmDefault
|
|
||||||
fun applyBasalConstraints(absoluteRate: Constraint<Double>, profile: Profile): Constraint<Double> {
|
|
||||||
return absoluteRate
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmDefault
|
|
||||||
fun applyBasalPercentConstraints(percentRate: Constraint<Int>, profile: Profile): Constraint<Int> {
|
|
||||||
return percentRate
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmDefault
|
|
||||||
fun applyBolusConstraints(insulin: Constraint<Double>): Constraint<Double> {
|
|
||||||
return insulin
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmDefault
|
|
||||||
fun applyExtendedBolusConstraints(insulin: Constraint<Double>): Constraint<Double> {
|
|
||||||
return insulin
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmDefault
|
|
||||||
fun applyCarbsConstraints(carbs: Constraint<Int>): Constraint<Int> {
|
|
||||||
return carbs
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmDefault
|
|
||||||
fun applyMaxIOBConstraints(maxIob: Constraint<Double>): Constraint<Double> {
|
|
||||||
return maxIob
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmDefault
|
|
||||||
fun isAutomationEnabled(value: Constraint<Boolean>): Constraint<Boolean> {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
}
|
}
|
Loading…
Reference in a new issue