Merge pull request #10 from nightscout/dev

Dev
This commit is contained in:
Andreas 2021-02-16 20:01:53 +01:00 committed by GitHub
commit c84161b83f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 1849 additions and 2269 deletions

View file

@ -131,6 +131,7 @@ class ProfileHelperActivity : NoSplashAppCompatActivity() {
// Default profile
binding.copytolocalprofile.setOnClickListener {
storeValues()
val age = ageUsed[tabSelected]
val weight = weightUsed[tabSelected]
val tdd = tddUsed[tabSelected]

View file

@ -22,7 +22,6 @@ import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin
import info.nightscout.androidaps.queue.Callback
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.ToastUtils
@ -42,7 +41,6 @@ class LoopDialog : DaggerDialogFragment() {
@Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var loopPlugin: LoopPlugin
@Inject lateinit var objectivesPlugin: ObjectivesPlugin
@Inject lateinit var activePlugin: ActivePluginProvider
@Inject lateinit var constraintChecker: ConstraintChecker
@Inject lateinit var commandQueue: CommandQueueProvider
@ -107,7 +105,7 @@ class LoopDialog : DaggerDialogFragment() {
binding.cancel.setOnClickListener { dismiss() }
refreshDialog = Runnable {
scheduleUpdateGUI("refreshDialog")
scheduleUpdateGUI()
loopHandler.postDelayed(refreshDialog, 15 * 1000L)
}
loopHandler.postDelayed(refreshDialog, 15 * 1000L)
@ -122,11 +120,11 @@ class LoopDialog : DaggerDialogFragment() {
var task: Runnable? = null
private fun scheduleUpdateGUI(from: String) {
private fun scheduleUpdateGUI() {
class UpdateRunnable : Runnable {
override fun run() {
updateGUI(from)
updateGUI("refreshDialog")
task = null
}
}
@ -140,8 +138,8 @@ class LoopDialog : DaggerDialogFragment() {
if (_binding == null) return
aapsLogger.debug("UpdateGUI from $from")
val pumpDescription: PumpDescription = activePlugin.activePump.pumpDescription
val closedLoopAllowed = objectivesPlugin.isClosedLoopAllowed(Constraint(true))
val lgsEnabled = objectivesPlugin.isLgsAllowed(Constraint(true))
val closedLoopAllowed = constraintChecker.isClosedLoopAllowed(Constraint(true))
val lgsEnabled = constraintChecker.isLgsAllowed(Constraint(true))
val apsMode = sp.getString(R.string.key_aps_mode, "open")
if (profileFunction.isProfileValid("LoopDialogUpdateGUI")) {
if (loopPlugin.isEnabled(PluginType.LOOP)) {

View file

@ -270,7 +270,7 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() {
val end = start + T.hours(rangeToDisplay.toLong()).msecs()
iobCobCalculatorPluginHistory.stopCalculation(from)
iobCobCalculatorPluginHistory.clearCache()
iobCobCalculatorPluginHistory.runCalculation(from, end, true, false, eventCustomCalculationFinished)
iobCobCalculatorPluginHistory.runCalculation(from, end, bgDataReload = true, limitDataToOldestAvailable = false, cause = eventCustomCalculationFinished)
}
}
@ -345,8 +345,8 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() {
menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] -> useDSForScale = true
}
var alignIobScale = menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]
var alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]
val alignIobScale = menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]
val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]
if (menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal]) secondGraphData.addAbsIob(fromTime, toTime, useABSForScale, 1.0)
if (menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]) secondGraphData.addIob(fromTime, toTime, useIobForScale, 1.0, menuChartSettings[g + 1][OverviewMenus.CharType.PRE.ordinal], alignIobScale)
@ -365,7 +365,7 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() {
}
// set manual x bounds to have nice steps
graphData.setNumVerticalLables()
graphData.setNumVerticalLabels()
graphData.formatAxis(fromTime, toTime)
}
// finally enforce drawing of graphs in UI thread

View file

@ -61,6 +61,7 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader
var scriptDebug = ""
private set
@Suppress("SpellCheckingInspection")
operator fun invoke(): DetermineBasalResultAMA? {
aapsLogger.debug(LTag.APS, ">>> Invoking determine_basal <<<")
aapsLogger.debug(LTag.APS, "Glucose status: " + glucoseStatus.toString().also { glucoseStatusParam = it })
@ -142,6 +143,7 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader
return determineBasalResultAMA
}
@Suppress("SpellCheckingInspection")
@Throws(JSONException::class) fun setData(profile: Profile,
maxIob: Double,
maxBasal: Double,
@ -149,7 +151,7 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader
maxBg: Double,
targetBg: Double,
basalRate: Double,
iobArray: Array<IobTotal?>?,
iobArray: Array<IobTotal>,
glucoseStatus: GlucoseStatus,
mealData: MealData,
autosensDataRatio: Double,
@ -196,12 +198,12 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader
this.glucoseStatus = JSONObject()
this.glucoseStatus.put("glucose", glucoseStatus.glucose)
if (sp.getBoolean(R.string.key_always_use_shortavg, false)) {
this.glucoseStatus.put("delta", glucoseStatus.short_avgdelta)
this.glucoseStatus.put("delta", glucoseStatus.shortAvgDelta)
} else {
this.glucoseStatus.put("delta", glucoseStatus.delta)
}
this.glucoseStatus.put("short_avgdelta", glucoseStatus.short_avgdelta)
this.glucoseStatus.put("long_avgdelta", glucoseStatus.long_avgdelta)
this.glucoseStatus.put("short_avgdelta", glucoseStatus.shortAvgDelta)
this.glucoseStatus.put("long_avgdelta", glucoseStatus.longAvgDelta)
this.mealData = JSONObject()
this.mealData.put("carbs", mealData.carbs)
this.mealData.put("boluses", mealData.boluses)

View file

@ -241,12 +241,12 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader:
mGlucoseStatus.put("glucose", glucoseStatus.glucose)
mGlucoseStatus.put("noise", glucoseStatus.noise)
if (sp.getBoolean(R.string.key_always_use_shortavg, false)) {
mGlucoseStatus.put("delta", glucoseStatus.short_avgdelta)
mGlucoseStatus.put("delta", glucoseStatus.shortAvgDelta)
} else {
mGlucoseStatus.put("delta", glucoseStatus.delta)
}
mGlucoseStatus.put("short_avgdelta", glucoseStatus.short_avgdelta)
mGlucoseStatus.put("long_avgdelta", glucoseStatus.long_avgdelta)
mGlucoseStatus.put("short_avgdelta", glucoseStatus.shortAvgDelta)
mGlucoseStatus.put("long_avgdelta", glucoseStatus.longAvgDelta)
mGlucoseStatus.put("date", glucoseStatus.date)
this.mealData.put("carbs", mealData.carbs)
this.mealData.put("boluses", mealData.boluses)

View file

@ -165,7 +165,7 @@ class ObjectivesPlugin @Inject constructor(
return value
}
fun isLgsAllowed(value: Constraint<Boolean>): Constraint<Boolean> {
override fun isLgsAllowed(value: Constraint<Boolean>): Constraint<Boolean> {
if (!objectives[MAXBASAL_OBJECTIVE].isStarted)
value.set(aapsLogger, false, String.format(resourceHelper.gs(R.string.objectivenotstarted), MAXBASAL_OBJECTIVE + 1), this)
return value

View file

@ -73,8 +73,8 @@ class TriggerDelta(injector: HasAndroidInjector) : Trigger(injector) {
false
}
val calculatedDelta = when (delta.deltaType) {
DeltaType.SHORT_AVERAGE -> glucoseStatus.short_avgdelta
DeltaType.LONG_AVERAGE -> glucoseStatus.long_avgdelta
DeltaType.SHORT_AVERAGE -> glucoseStatus.shortAvgDelta
DeltaType.LONG_AVERAGE -> glucoseStatus.longAvgDelta
else -> glucoseStatus.delta
}
if (comparator.value.check(calculatedDelta, Profile.toMgdl(delta.value, units))) {

View file

@ -130,7 +130,7 @@ class DataBroadcastPlugin @Inject constructor(
bundle.putString("units", profileFunction.getUnits()) // units used in AAPS "mg/dl" or "mmol"
bundle.putString("slopeArrow", lastBG.trendArrow.text) // direction arrow as string
bundle.putDouble("deltaMgdl", glucoseStatus.delta) // bg delta in mgdl
bundle.putDouble("avgDeltaMgdl", glucoseStatus.avgdelta) // average bg delta
bundle.putDouble("avgDeltaMgdl", glucoseStatus.avgDelta) // average bg delta
bundle.putDouble("high", defaultValueHelper.determineHighLine()) // predefined top value of in range (green area)
bundle.putDouble("low", defaultValueHelper.determineLowLine()) // predefined bottom value of in range
}

View file

@ -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");
}
}

View file

@ -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")
}
}

View file

@ -34,7 +34,8 @@ class MaintenancePlugin @Inject constructor(
private val nsSettingsStatus: NSSettingsStatus,
aapsLogger: AAPSLogger,
private val buildHelper: BuildHelper,
private val config: Config
private val config: Config,
private val loggerUtils: LoggerUtils
) : PluginBase(PluginDescription()
.mainType(PluginType.GENERAL)
.fragmentClass(MaintenanceFragment::class.java.name)
@ -51,8 +52,7 @@ class MaintenancePlugin @Inject constructor(
fun sendLogs() {
val recipient = sp.getString(R.string.key_maintenance_logs_email, "logs@androidaps.org")
val amount = sp.getInt(R.string.key_maintenance_logs_amount, 2)
val logDirectory = LoggerUtils.getLogDirectory()
val logs = getLogFiles(logDirectory, amount)
val logs = getLogFiles(amount)
val zipDir = context.getExternalFilesDir("exports")
val zipFile = File(zipDir, constructName())
aapsLogger.debug("zipFile: ${zipFile.absolutePath}")
@ -66,8 +66,7 @@ class MaintenancePlugin @Inject constructor(
//todo replace this with a call on startup of the application, specifically to remove
// unnecessary garbage from the log exports
fun deleteLogs() {
LoggerUtils.getLogDirectory()?.let { logDirectory ->
val logDir = File(logDirectory)
val logDir = File(loggerUtils.logDirectory)
val files = logDir.listFiles { _: File?, name: String ->
(name.startsWith("AndroidAPS") && name.endsWith(".zip"))
}
@ -81,7 +80,7 @@ class MaintenancePlugin @Inject constructor(
file.delete()
}
}
val exportDir = File(logDirectory, "exports")
val exportDir = File(loggerUtils.logDirectory, "exports")
if (exportDir.exists()) {
val expFiles = exportDir.listFiles()
for (file in expFiles) {
@ -90,7 +89,6 @@ class MaintenancePlugin @Inject constructor(
exportDir.delete()
}
}
}
/**
* 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.
*
* @param directory
* @param amount
* @return
*/
fun getLogFiles(directory: String, amount: Int): List<File> {
aapsLogger.debug("getting $amount logs from directory $directory")
val logDir = File(directory)
fun getLogFiles(amount: Int): List<File> {
aapsLogger.debug("getting $amount logs from directory ${loggerUtils.logDirectory}")
val logDir = File(loggerUtils.logDirectory)
val files = logDir.listFiles { _: File?, name: String ->
(name.startsWith("AndroidAPS")
&& (name.endsWith(".log")
|| name.endsWith(".zip") && !name.endsWith(LoggerUtils.SUFFIX)))
|| name.endsWith(".zip") && !name.endsWith(loggerUtils.suffix)))
}
Arrays.sort(files) { f1: File, f2: File -> f2.name.compareTo(f1.name) }
val result = listOf(*files)
@ -139,7 +136,7 @@ class MaintenancePlugin @Inject constructor(
* @return
*/
private fun constructName(): String {
return "AndroidAPS_LOG_" + Date().time + LoggerUtils.SUFFIX
return "AndroidAPS_LOG_" + Date().time + loggerUtils.suffix
}
private fun zip(zipFile: File?, files: List<File>) {

View 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.setTextColor(color)
binding.infoLayout.delta.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units)
binding.infoLayout.avgDelta.text = Profile.toSignedUnitsString(glucoseStatus.short_avgdelta, glucoseStatus.short_avgdelta * Constants.MGDL_TO_MMOLL, units)
binding.infoLayout.longAvgDelta.text = Profile.toSignedUnitsString(glucoseStatus.long_avgdelta, glucoseStatus.long_avgdelta * Constants.MGDL_TO_MMOLL, units)
binding.infoLayout.avgDelta.text = Profile.toSignedUnitsString(glucoseStatus.shortAvgDelta, glucoseStatus.shortAvgDelta * Constants.MGDL_TO_MMOLL, units)
binding.infoLayout.longAvgDelta.text = Profile.toSignedUnitsString(glucoseStatus.longAvgDelta, glucoseStatus.longAvgDelta * Constants.MGDL_TO_MMOLL, units)
} else {
binding.infoLayout.delta.text = "Δ " + resourceHelper.gs(R.string.notavailable)
binding.infoLayout.avgDelta.text = ""
@ -861,7 +861,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
graphData.addTreatments(fromTime, endTime)
// set manual x bounds to have nice steps
graphData.setNumVerticalLables()
graphData.setNumVerticalLabels()
graphData.formatAxis(fromTime, endTime)

View file

@ -88,7 +88,7 @@ class GraphData(
addSeries(PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] }))
}
internal fun setNumVerticalLables() {
internal fun setNumVerticalLabels() {
graph.gridLabelRenderer.numVerticalLabels = if (units == Constants.MGDL) (maxY / 40 + 1).toInt() else (maxY / 2 + 1).toInt()
}
@ -291,7 +291,7 @@ class GraphData(
fun addActivity(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) {
val actArrayHist: MutableList<ScaledDataPoint> = ArrayList()
val actArrayPred: MutableList<ScaledDataPoint> = ArrayList()
val actArrayPrediction: MutableList<ScaledDataPoint> = ArrayList()
val now = System.currentTimeMillis().toDouble()
val actScale = Scale()
var total: IobTotal
@ -305,7 +305,7 @@ class GraphData(
}
total = iobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time, profile)
val act: Double = total.activity
if (time <= now) actArrayHist.add(ScaledDataPoint(time, act, actScale)) else actArrayPred.add(ScaledDataPoint(time, act, actScale))
if (time <= now) actArrayHist.add(ScaledDataPoint(time, act, actScale)) else actArrayPrediction.add(ScaledDataPoint(time, act, actScale))
maxIAValue = max(maxIAValue, abs(act))
time += 5 * 60 * 1000L
}
@ -314,7 +314,7 @@ class GraphData(
it.color = resourceHelper.gc(R.color.activity)
it.thickness = 3
})
addSeries(FixedLineGraphSeries(Array(actArrayPred.size) { i -> actArrayPred[i] }).also {
addSeries(FixedLineGraphSeries(Array(actArrayPrediction.size) { i -> actArrayPrediction[i] }).also {
it.setCustomPaint(Paint().also { paint ->
paint.style = Paint.Style.STROKE
paint.strokeWidth = 3f
@ -332,7 +332,7 @@ class GraphData(
//Function below show -BGI to be able to compare curves with deviations
fun addMinusBGI(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double, devBgiScale: Boolean) {
val bgiArrayHist: MutableList<ScaledDataPoint> = ArrayList()
val bgiArrayPred: MutableList<ScaledDataPoint> = ArrayList()
val bgiArrayPrediction: MutableList<ScaledDataPoint> = ArrayList()
val now = System.currentTimeMillis().toDouble()
val bgiScale = Scale()
var total: IobTotal
@ -348,7 +348,7 @@ class GraphData(
total = iobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time, profile)
val bgi: Double = total.activity * profile.getIsfMgdl(time) * 5.0
if (time <= now) bgiArrayHist.add(ScaledDataPoint(time, bgi, bgiScale)) else bgiArrayPred.add(ScaledDataPoint(time, bgi, bgiScale))
if (time <= now) bgiArrayHist.add(ScaledDataPoint(time, bgi, bgiScale)) else bgiArrayPrediction.add(ScaledDataPoint(time, bgi, bgiScale))
maxBGIValue = max(maxBGIValue, max(abs(bgi), deviation))
time += 5 * 60 * 1000L
}
@ -357,7 +357,7 @@ class GraphData(
it.color = resourceHelper.gc(R.color.bgi)
it.thickness = 3
})
addSeries(FixedLineGraphSeries(Array(bgiArrayPred.size) { i -> bgiArrayPred[i] }).also {
addSeries(FixedLineGraphSeries(Array(bgiArrayPrediction.size) { i -> bgiArrayPrediction[i] }).also {
it.setCustomPaint(Paint().also { paint ->
paint.style = Paint.Style.STROKE
paint.strokeWidth = 3f
@ -386,7 +386,7 @@ class GraphData(
var absIob = 0.0
if (profile != null) {
iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time, profile).iob
if (absScale) absIob = iobCobCalculatorPlugin.calculateAbsInsulinFromTreatmentsAndTempsSynchronized(time, profile).iob
if (absScale) absIob = iobCobCalculatorPlugin.calculateAbsInsulinFromTreatmentsAndTempsSynchronized(time).iob
}
if (abs(lastIob - iob) > 0.02) {
if (abs(lastIob - iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, iobScale))
@ -406,22 +406,22 @@ class GraphData(
val autosensData = iobCobCalculatorPlugin.getLastAutosensDataSynchronized("GraphData")
val lastAutosensResult = autosensData?.autosensResult ?: AutosensResult()
val isTempTarget = treatmentsPlugin.getTempTargetFromHistory(System.currentTimeMillis()) != null
val iobPred: MutableList<DataPointWithLabelInterface> = ArrayList()
val iobPredArray = iobCobCalculatorPlugin.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget)
for (i in iobPredArray) {
iobPred.add(i.setColor(resourceHelper.gc(R.color.iobPredAS)))
val iobPrediction: MutableList<DataPointWithLabelInterface> = ArrayList()
val iobPredictionArray = iobCobCalculatorPlugin.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget)
for (i in iobPredictionArray) {
iobPrediction.add(i.setColor(resourceHelper.gc(R.color.iobPredAS)))
maxIobValueFound = max(maxIobValueFound, abs(i.iob))
}
addSeries(PointsWithLabelGraphSeries(Array(iobPred.size) { i -> iobPred[i] }))
val iobPred2: MutableList<DataPointWithLabelInterface> = ArrayList()
val iobPredArray2 = iobCobCalculatorPlugin.calculateIobArrayForSMB(AutosensResult(), SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget)
for (i in iobPredArray2) {
iobPred2.add(i.setColor(resourceHelper.gc(R.color.iobPred)))
addSeries(PointsWithLabelGraphSeries(Array(iobPrediction.size) { i -> iobPrediction[i] }))
val iobPrediction2: MutableList<DataPointWithLabelInterface> = ArrayList()
val iobPredictionArray2 = iobCobCalculatorPlugin.calculateIobArrayForSMB(AutosensResult(), SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget)
for (i in iobPredictionArray2) {
iobPrediction2.add(i.setColor(resourceHelper.gc(R.color.iobPred)))
maxIobValueFound = max(maxIobValueFound, abs(i.iob))
}
addSeries(PointsWithLabelGraphSeries(Array(iobPred2.size) { i -> iobPred2[i] }))
aapsLogger.debug(LTag.AUTOSENS, "IOB pred for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredArray))
aapsLogger.debug(LTag.AUTOSENS, "IOB pred for AS=" + DecimalFormatter.to2Decimal(1.0) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredArray2))
addSeries(PointsWithLabelGraphSeries(Array(iobPrediction2.size) { i -> iobPrediction2[i] }))
aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredictionArray))
aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(1.0) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredictionArray2))
}
if (useForScale) {
maxY = maxIobValueFound
@ -442,7 +442,7 @@ class GraphData(
while (time <= toTime) {
val profile = profileFunction.getProfile(time)
var iob = 0.0
if (profile != null) iob = iobCobCalculatorPlugin.calculateAbsInsulinFromTreatmentsAndTempsSynchronized(time, profile).iob
if (profile != null) iob = iobCobCalculatorPlugin.calculateAbsInsulinFromTreatmentsAndTempsSynchronized(time).iob
if (abs(lastIob - iob) > 0.02) {
if (abs(lastIob - iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, iobScale))
iobArray.add(ScaledDataPoint(time, iob, iobScale))

View file

@ -140,7 +140,7 @@ class PersistentNotificationPlugin @Inject constructor(
line1 = line1aa
if (glucoseStatus != null) {
line1 += (" Δ" + Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units)
+ " avgΔ" + Profile.toSignedUnitsString(glucoseStatus.avgdelta, glucoseStatus.avgdelta * Constants.MGDL_TO_MMOLL, units))
+ " avgΔ" + Profile.toSignedUnitsString(glucoseStatus.avgDelta, glucoseStatus.avgDelta * Constants.MGDL_TO_MMOLL, units))
line1aa += " " + lastBG.trendArrow.symbol
} else {
line1 += " " +

View file

@ -320,9 +320,9 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
dataMap.putString("delta", "--");
dataMap.putString("avgDelta", "--");
} else {
dataMap.putString("slopeArrow", slopeArrow(glucoseStatus.delta));
dataMap.putString("delta", deltastring(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units));
dataMap.putString("avgDelta", deltastring(glucoseStatus.avgdelta, glucoseStatus.avgdelta * Constants.MGDL_TO_MMOLL, units));
dataMap.putString("slopeArrow", slopeArrow(glucoseStatus.getDelta()));
dataMap.putString("delta", deltastring(glucoseStatus.getDelta(), glucoseStatus.getDelta() * Constants.MGDL_TO_MMOLL, units));
dataMap.putString("avgDelta", deltastring(glucoseStatus.getAvgDelta(), glucoseStatus.getAvgDelta() * Constants.MGDL_TO_MMOLL, units));
}
dataMap.putLong("sgvLevel", sgvLevel);
dataMap.putDouble("sgvDouble", lastBG.getValue());

View file

@ -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);
}
}

View file

@ -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
}
}

View file

@ -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;
}

View file

@ -0,0 +1,8 @@
package info.nightscout.androidaps.plugins.iob.iobCobCalculator
class BasalData {
var basal = 0.0
var tempBasalAbsolute = 0.0
var isTempBasalRunning = false
}

View file

@ -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;
}
}

View file

@ -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
}
}

View file

@ -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();
}
}

View file

@ -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
}
}
}

View file

@ -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;
}
}

View file

@ -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
}
}
}

View file

@ -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);
}
}
}

View file

@ -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)
}
}
}

View file

@ -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);
}
}
}

View file

@ -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)
}
}
}

View file

@ -24,13 +24,12 @@ import info.nightscout.androidaps.db.ProfileSwitch;
import info.nightscout.androidaps.interfaces.IobCobCalculatorInterface;
import info.nightscout.androidaps.interfaces.PluginDescription;
import info.nightscout.androidaps.interfaces.PluginType;
import info.nightscout.androidaps.interfaces.ProfileFunction;
import info.nightscout.androidaps.logging.AAPSLogger;
import info.nightscout.androidaps.logging.LTag;
import info.nightscout.androidaps.interfaces.ProfileFunction;
import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults;
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData;
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult;
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin;
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData;
import info.nightscout.androidaps.utils.DateUtil;
import info.nightscout.androidaps.utils.resources.ResourceHelper;
import info.nightscout.androidaps.utils.sharedPreferences.SP;
@ -161,7 +160,7 @@ public class SensitivityAAPSPlugin extends AbstractSensitivityPlugin {
Arrays.sort(deviations);
double percentile = IobCobCalculatorPlugin.percentile(deviations, 0.50);
double percentile = IobCobCalculatorPlugin.Companion.percentile(deviations, 0.50);
double basalOff = percentile * (60.0 / 5.0) / sens;
double ratio = 1 + (basalOff / profile.getMaxDailyBasal());

View file

@ -207,8 +207,8 @@ public class SensitivityOref1Plugin extends AbstractSensitivityPlugin {
getAapsLogger().debug(LTag.AUTOSENS, "Records: " + index + " " + pastSensitivity);
Arrays.sort(deviations);
double pSensitive = IobCobCalculatorPlugin.percentile(deviations, 0.50);
double pResistant = IobCobCalculatorPlugin.percentile(deviations, 0.50);
double pSensitive = IobCobCalculatorPlugin.Companion.percentile(deviations, 0.50);
double pResistant = IobCobCalculatorPlugin.Companion.percentile(deviations, 0.50);
double basalOff = 0;

View file

@ -8,13 +8,13 @@ import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class BuildHelper @Inject constructor(private val config: Config) {
class BuildHelper @Inject constructor(private val config: Config, loggerUtils: LoggerUtils) {
private var devBranch = false
private var engineeringMode = false
init {
val extFilesDir = LoggerUtils.getLogDirectory()
val extFilesDir = loggerUtils.logDirectory
val engineeringModeSemaphore = File(extFilesDir, "engineering__mode")
engineeringMode = engineeringModeSemaphore.exists() && engineeringModeSemaphore.isFile

View file

@ -194,7 +194,7 @@ class BolusWizard @Inject constructor(
glucoseStatus = GlucoseStatus(injector).glucoseStatusData
glucoseStatus?.let {
if (useTrend) {
trend = it.short_avgdelta
trend = it.shortAvgDelta
insulinFromTrend = Profile.fromMgdlToUnits(trend, profileFunction.getUnits()) * 3 / sens
}
}

View file

@ -112,9 +112,9 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec
var trend = false
if (useTrend() == YES) {
trend = true
} else if (useTrend() == POSITIVE_ONLY && glucoseStatus != null && glucoseStatus.short_avgdelta > 0) {
} else if (useTrend() == POSITIVE_ONLY && glucoseStatus != null && glucoseStatus.shortAvgDelta > 0) {
trend = true
} else if (useTrend() == NEGATIVE_ONLY && glucoseStatus != null && glucoseStatus.short_avgdelta < 0) {
} else if (useTrend() == NEGATIVE_ONLY && glucoseStatus != null && glucoseStatus.shortAvgDelta < 0) {
trend = true
}
val percentage = sp.getDouble(R.string.key_boluswizard_percentage, 100.0)

View file

@ -23,7 +23,7 @@ import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
@RunWith(PowerMockRunner::class)
@PrepareForTest(Profile::class)
@PrepareForTest(Profile::class, IobCobCalculatorPlugin::class)
class QuickWizardTest : TestBase() {
@Mock lateinit var sp: SP
@ -68,7 +68,7 @@ class QuickWizardTest : TestBase() {
@Test fun test() {
quickWizard.setData(array)
Assert.assertEquals("Lunch", quickWizard.get(1).buttonText())
Assert.assertEquals("Lunch", quickWizard[1].buttonText())
}
@Test fun active() {

View file

@ -18,6 +18,7 @@ import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin
import info.nightscout.androidaps.plugins.constraints.objectives.objectives.Objective
import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin
import info.nightscout.androidaps.plugins.general.maintenance.LoggerUtils
import info.nightscout.androidaps.plugins.general.nsclient.NSUpload
import info.nightscout.androidaps.plugins.general.nsclient.UploadQueue
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
@ -47,7 +48,11 @@ import java.util.*
* Created by mike on 18.03.2018.
*/
@RunWith(PowerMockRunner::class)
@PrepareForTest(MainApp::class, ConfigBuilderPlugin::class, ConstraintChecker::class, SP::class, Context::class, OpenAPSAMAPlugin::class, OpenAPSSMBPlugin::class, TreatmentsPlugin::class, TreatmentService::class, VirtualPumpPlugin::class, DetailedBolusInfoStorage::class, GlimpPlugin::class, Profiler::class, UserEntryLogger::class)
@PrepareForTest(
MainApp::class, ConfigBuilderPlugin::class, ConstraintChecker::class, SP::class, Context::class,
OpenAPSAMAPlugin::class, OpenAPSSMBPlugin::class, TreatmentsPlugin::class, TreatmentService::class,
VirtualPumpPlugin::class, DetailedBolusInfoStorage::class, GlimpPlugin::class, Profiler::class,
UserEntryLogger::class, IobCobCalculatorPlugin::class, LoggerUtils::class)
class ConstraintsCheckerTest : TestBaseWithProfile() {
@Mock lateinit var activePlugin: ActivePluginProvider
@ -63,8 +68,8 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
@Mock lateinit var nsUpload: NSUpload
@Mock lateinit var uploadQueue: UploadQueue
@Mock lateinit var uel: UserEntryLogger
@Mock lateinit var loggerUtils: LoggerUtils
private var buildHelper = BuildHelper(Config())
lateinit var danaPump: DanaPump
lateinit var constraintChecker: ConstraintChecker
@ -124,7 +129,7 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
insightPlugin = LocalInsightPlugin(injector, aapsLogger, rxBus, resourceHelper, treatmentsPlugin, sp, commandQueue, profileFunction, nsUpload, context, uploadQueue, Config(), dateUtil)
openAPSSMBPlugin = OpenAPSSMBPlugin(injector, aapsLogger, rxBus, constraintChecker, resourceHelper, profileFunction, context, activePlugin, treatmentsPlugin, iobCobCalculatorPlugin, hardLimits, profiler, sp)
openAPSAMAPlugin = OpenAPSAMAPlugin(injector, aapsLogger, rxBus, constraintChecker, resourceHelper, profileFunction, context, activePlugin, treatmentsPlugin, iobCobCalculatorPlugin, hardLimits, profiler, fabricPrivacy)
safetyPlugin = SafetyPlugin(injector, aapsLogger, resourceHelper, sp, rxBus, constraintChecker, openAPSAMAPlugin, openAPSSMBPlugin, sensitivityOref1Plugin, activePlugin, hardLimits, buildHelper, treatmentsPlugin, Config())
safetyPlugin = SafetyPlugin(injector, aapsLogger, resourceHelper, sp, rxBus, constraintChecker, openAPSAMAPlugin, openAPSSMBPlugin, sensitivityOref1Plugin, activePlugin, hardLimits, BuildHelper(Config(), loggerUtils), treatmentsPlugin, Config())
val constraintsPluginsList = ArrayList<PluginBase>()
constraintsPluginsList.add(safetyPlugin)
constraintsPluginsList.add(objectivesPlugin)

View file

@ -33,7 +33,9 @@ import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
@RunWith(PowerMockRunner::class)
@PrepareForTest(ConstraintChecker::class, VirtualPumpPlugin::class, FabricPrivacy::class, ReceiverStatusStore::class)
@PrepareForTest(
ConstraintChecker::class, VirtualPumpPlugin::class, FabricPrivacy::class, ReceiverStatusStore::class,
IobCobCalculatorPlugin::class)
class LoopPluginTest : TestBase() {
@Mock lateinit var sp: SP

View file

@ -19,7 +19,7 @@ import org.junit.Before
import org.mockito.Mock
import org.powermock.core.classloader.annotations.PrepareForTest
@PrepareForTest(LastLocationDataContainer::class, AutomationPlugin::class)
@PrepareForTest(LastLocationDataContainer::class, AutomationPlugin::class, IobCobCalculatorPlugin::class)
open class TriggerTestBase : TestBaseWithProfile() {
@Mock lateinit var sp: SP

View file

@ -13,12 +13,13 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
import java.io.File
@RunWith(PowerMockRunner::class)
@PrepareForTest(NSSettingsStatus::class, BuildHelper::class)
@PrepareForTest(NSSettingsStatus::class, BuildHelper::class, LoggerUtils::class)
class MaintenancePluginTest : TestBase() {
@Mock lateinit var injector: HasAndroidInjector
@ -27,28 +28,29 @@ class MaintenancePluginTest : TestBase() {
@Mock lateinit var sp: SP
@Mock lateinit var nsSettingsStatus: NSSettingsStatus
@Mock lateinit var buildHelper: BuildHelper
@Mock lateinit var loggerUtils: LoggerUtils
lateinit var sut: MaintenancePlugin
@Before
fun mock() {
sut = MaintenancePlugin(injector, context, resourceHelper, sp, nsSettingsStatus, aapsLogger, buildHelper, Config())
sut = MaintenancePlugin(injector, context, resourceHelper, sp, nsSettingsStatus, aapsLogger, buildHelper, Config(), loggerUtils)
`when`(loggerUtils.suffix).thenReturn(".log.zip")
`when`(loggerUtils.logDirectory).thenReturn("src/test/res/logger")
}
@Test fun logfilesTest() {
val logDirectory = "src/test/res/logger"
var logs = sut.getLogFiles(logDirectory, 2)
@Test fun logFilesTest() {
var logs = sut.getLogFiles(2)
Assert.assertEquals(2, logs.size)
Assert.assertEquals("AndroidAPS.log", logs[0].name)
Assert.assertEquals("AndroidAPS.2018-01-03_01-01-00.1.zip", logs[1].name)
logs = sut.getLogFiles(logDirectory, 10)
logs = sut.getLogFiles(10)
Assert.assertEquals(4, logs.size)
}
@Test
fun zipLogsTest() {
val logDirectory = "src/test/res/logger"
val logs = sut.getLogFiles(logDirectory, 2)
val logs = sut.getLogFiles(2)
val name = "AndroidAPS.log.zip"
var zipFile = File("build/$name")
zipFile = sut.zipLogs(zipFile, logs)

View file

@ -52,9 +52,9 @@ class GlucoseStatusTest : TestBase() {
val glucoseStatus = GlucoseStatus(injector).glucoseStatusData!!
Assert.assertEquals(214.0, glucoseStatus.glucose, 0.001)
Assert.assertEquals(-2.0, glucoseStatus.delta, 0.001)
Assert.assertEquals(-2.5, glucoseStatus.short_avgdelta, 0.001) // -2 -2.5 -3 deltas are relative to current value
Assert.assertEquals(-2.5, glucoseStatus.avgdelta, 0.001) // the same as short_avgdelta
Assert.assertEquals(-2.0, glucoseStatus.long_avgdelta, 0.001) // -2 -2 -2 -2
Assert.assertEquals(-2.5, glucoseStatus.shortAvgDelta, 0.001) // -2 -2.5 -3 deltas are relative to current value
Assert.assertEquals(-2.5, glucoseStatus.avgDelta, 0.001) // the same as short_avgdelta
Assert.assertEquals(-2.0, glucoseStatus.longAvgDelta, 0.001) // -2 -2 -2 -2
Assert.assertEquals(1514766900000L, glucoseStatus.date) // latest date
}
@ -63,9 +63,9 @@ class GlucoseStatusTest : TestBase() {
val glucoseStatus: GlucoseStatus = GlucoseStatus(injector).glucoseStatusData!!
Assert.assertEquals(215.0, glucoseStatus.glucose, 0.001) // (214+216) / 2
Assert.assertEquals(-1.0, glucoseStatus.delta, 0.001)
Assert.assertEquals(-1.0, glucoseStatus.short_avgdelta, 0.001)
Assert.assertEquals(-1.0, glucoseStatus.avgdelta, 0.001)
Assert.assertEquals(0.0, glucoseStatus.long_avgdelta, 0.001)
Assert.assertEquals(-1.0, glucoseStatus.shortAvgDelta, 0.001)
Assert.assertEquals(-1.0, glucoseStatus.avgDelta, 0.001)
Assert.assertEquals(0.0, glucoseStatus.longAvgDelta, 0.001)
Assert.assertEquals(1514766900000L, glucoseStatus.date) // latest date, even when averaging
}
@ -74,9 +74,9 @@ class GlucoseStatusTest : TestBase() {
val glucoseStatus: GlucoseStatus = GlucoseStatus(injector).glucoseStatusData!!
Assert.assertEquals(214.0, glucoseStatus.glucose, 0.001)
Assert.assertEquals(0.0, glucoseStatus.delta, 0.001)
Assert.assertEquals(0.0, glucoseStatus.short_avgdelta, 0.001) // -2 -2.5 -3 deltas are relative to current value
Assert.assertEquals(0.0, glucoseStatus.avgdelta, 0.001) // the same as short_avgdelta
Assert.assertEquals(0.0, glucoseStatus.long_avgdelta, 0.001) // -2 -2 -2 -2
Assert.assertEquals(0.0, glucoseStatus.shortAvgDelta, 0.001) // -2 -2.5 -3 deltas are relative to current value
Assert.assertEquals(0.0, glucoseStatus.avgDelta, 0.001) // the same as short_avgdelta
Assert.assertEquals(0.0, glucoseStatus.longAvgDelta, 0.001) // -2 -2 -2 -2
Assert.assertEquals(1514766900000L, glucoseStatus.date) // latest date
}
@ -107,9 +107,9 @@ class GlucoseStatusTest : TestBase() {
val glucoseStatus: GlucoseStatus = GlucoseStatus(injector).glucoseStatusData!!
Assert.assertEquals(100.0, glucoseStatus.glucose, 0.001) //
Assert.assertEquals(-10.0, glucoseStatus.delta, 0.001)
Assert.assertEquals(-10.0, glucoseStatus.short_avgdelta, 0.001)
Assert.assertEquals(-10.0, glucoseStatus.avgdelta, 0.001)
Assert.assertEquals(-10.0, glucoseStatus.long_avgdelta, 0.001)
Assert.assertEquals(-10.0, glucoseStatus.shortAvgDelta, 0.001)
Assert.assertEquals(-10.0, glucoseStatus.avgDelta, 0.001)
Assert.assertEquals(-10.0, glucoseStatus.longAvgDelta, 0.001)
Assert.assertEquals(1514766900000L, glucoseStatus.date) // latest date
}

View file

@ -13,7 +13,6 @@ import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin
import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.resources.ResourceHelper
@ -40,7 +39,6 @@ class IobCobCalculatorPluginTest : TestBase() {
@Mock lateinit var sensitivityOref1Plugin: SensitivityOref1Plugin
@Mock lateinit var sensitivityAAPSPlugin: SensitivityAAPSPlugin
@Mock lateinit var sensitivityWeightedAveragePlugin: SensitivityWeightedAveragePlugin
@Mock lateinit var defaultValueHelper: DefaultValueHelper
@Mock lateinit var fabricPrivacy: FabricPrivacy
@Mock lateinit var dateUtil: DateUtil
@Mock lateinit var repository: AppRepository
@ -137,9 +135,9 @@ class IobCobCalculatorPluginTest : TestBase() {
iobCobCalculatorPlugin.bgReadings = bgReadingList
Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData)
iobCobCalculatorPlugin.createBucketedData()
Assert.assertEquals(bgReadingList[0].timestamp, iobCobCalculatorPlugin.bucketedData[0].timestamp)
Assert.assertEquals(bgReadingList[3].timestamp, iobCobCalculatorPlugin.bucketedData[3].timestamp)
Assert.assertEquals(bgReadingList.size.toLong(), iobCobCalculatorPlugin.bucketedData.size.toLong())
Assert.assertEquals(bgReadingList[0].timestamp, iobCobCalculatorPlugin.bucketedData!![0].timestamp)
Assert.assertEquals(bgReadingList[3].timestamp, iobCobCalculatorPlugin.bucketedData!![3].timestamp)
Assert.assertEquals(bgReadingList.size.toLong(), iobCobCalculatorPlugin.bucketedData!!.size.toLong())
// Missing value should be replaced
bgReadingList.clear()
@ -149,9 +147,9 @@ class IobCobCalculatorPluginTest : TestBase() {
iobCobCalculatorPlugin.bgReadings = bgReadingList
Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData)
iobCobCalculatorPlugin.createBucketedData()
Assert.assertEquals(bgReadingList[0].timestamp, iobCobCalculatorPlugin.bucketedData[0].timestamp)
Assert.assertEquals(bgReadingList[2].timestamp, iobCobCalculatorPlugin.bucketedData[3].timestamp)
Assert.assertEquals(bgReadingList.size + 1.toLong(), iobCobCalculatorPlugin.bucketedData.size.toLong())
Assert.assertEquals(bgReadingList[0].timestamp, iobCobCalculatorPlugin.bucketedData!![0].timestamp)
Assert.assertEquals(bgReadingList[2].timestamp, iobCobCalculatorPlugin.bucketedData!![3].timestamp)
Assert.assertEquals(bgReadingList.size + 1.toLong(), iobCobCalculatorPlugin.bucketedData!!.size.toLong())
// drift should be cleared
bgReadingList.clear()
@ -163,11 +161,11 @@ class IobCobCalculatorPluginTest : TestBase() {
iobCobCalculatorPlugin.bgReadings = bgReadingList
Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData)
iobCobCalculatorPlugin.createBucketedData()
Assert.assertEquals(T.mins(20).msecs(), iobCobCalculatorPlugin.bucketedData[0].timestamp)
Assert.assertEquals(T.mins(15).msecs(), iobCobCalculatorPlugin.bucketedData[1].timestamp)
Assert.assertEquals(T.mins(10).msecs(), iobCobCalculatorPlugin.bucketedData[2].timestamp)
Assert.assertEquals(T.mins(5).msecs(), iobCobCalculatorPlugin.bucketedData[3].timestamp)
Assert.assertEquals(bgReadingList.size.toLong(), iobCobCalculatorPlugin.bucketedData.size.toLong())
Assert.assertEquals(T.mins(20).msecs(), iobCobCalculatorPlugin.bucketedData!![0].timestamp)
Assert.assertEquals(T.mins(15).msecs(), iobCobCalculatorPlugin.bucketedData!![1].timestamp)
Assert.assertEquals(T.mins(10).msecs(), iobCobCalculatorPlugin.bucketedData!![2].timestamp)
Assert.assertEquals(T.mins(5).msecs(), iobCobCalculatorPlugin.bucketedData!![3].timestamp)
Assert.assertEquals(bgReadingList.size.toLong(), iobCobCalculatorPlugin.bucketedData!!.size.toLong())
// bucketed data should return null if not enough bg data
bgReadingList.clear()
@ -186,13 +184,13 @@ class IobCobCalculatorPluginTest : TestBase() {
iobCobCalculatorPlugin.bgReadings = bgReadingList
Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData)
iobCobCalculatorPlugin.createBucketedData()
Assert.assertEquals(T.mins(50).msecs(), iobCobCalculatorPlugin.bucketedData[0].timestamp)
Assert.assertEquals(T.mins(20).msecs(), iobCobCalculatorPlugin.bucketedData[6].timestamp)
Assert.assertEquals(7, iobCobCalculatorPlugin.bucketedData.size.toLong())
Assert.assertEquals(100.0, iobCobCalculatorPlugin.bucketedData[0].value, 1.0)
Assert.assertEquals(90.0, iobCobCalculatorPlugin.bucketedData[1].value, 1.0)
Assert.assertEquals(50.0, iobCobCalculatorPlugin.bucketedData[5].value, 1.0)
Assert.assertEquals(40.0, iobCobCalculatorPlugin.bucketedData[6].value, 1.0)
Assert.assertEquals(T.mins(50).msecs(), iobCobCalculatorPlugin.bucketedData!![0].timestamp)
Assert.assertEquals(T.mins(20).msecs(), iobCobCalculatorPlugin.bucketedData!![6].timestamp)
Assert.assertEquals(7, iobCobCalculatorPlugin.bucketedData!!.size.toLong())
Assert.assertEquals(100.0, iobCobCalculatorPlugin.bucketedData!![0].value, 1.0)
Assert.assertEquals(90.0, iobCobCalculatorPlugin.bucketedData!![1].value, 1.0)
Assert.assertEquals(50.0, iobCobCalculatorPlugin.bucketedData!![5].value, 1.0)
Assert.assertEquals(40.0, iobCobCalculatorPlugin.bucketedData!![6].value, 1.0)
// non 5min data should be reconstructed
bgReadingList.clear()
@ -202,16 +200,16 @@ class IobCobCalculatorPluginTest : TestBase() {
iobCobCalculatorPlugin.bgReadings = bgReadingList
Assert.assertEquals(false, iobCobCalculatorPlugin.isAbout5minData)
iobCobCalculatorPlugin.createBucketedData()
Assert.assertEquals(T.mins(50).msecs(), iobCobCalculatorPlugin.bucketedData[0].timestamp)
Assert.assertEquals(T.mins(20).msecs(), iobCobCalculatorPlugin.bucketedData[6].timestamp)
Assert.assertEquals(7, iobCobCalculatorPlugin.bucketedData.size.toLong())
Assert.assertEquals(100.0, iobCobCalculatorPlugin.bucketedData[0].value, 1.0)
Assert.assertEquals(90.0, iobCobCalculatorPlugin.bucketedData[1].value, 1.0)
Assert.assertEquals(50.0, iobCobCalculatorPlugin.bucketedData[5].value, 1.0)
Assert.assertEquals(40.0, iobCobCalculatorPlugin.bucketedData[6].value, 1.0)
Assert.assertEquals(T.mins(50).msecs(), iobCobCalculatorPlugin.bucketedData!![0].timestamp)
Assert.assertEquals(T.mins(20).msecs(), iobCobCalculatorPlugin.bucketedData!![6].timestamp)
Assert.assertEquals(7, iobCobCalculatorPlugin.bucketedData!!.size.toLong())
Assert.assertEquals(100.0, iobCobCalculatorPlugin.bucketedData!![0].value, 1.0)
Assert.assertEquals(90.0, iobCobCalculatorPlugin.bucketedData!![1].value, 1.0)
Assert.assertEquals(50.0, iobCobCalculatorPlugin.bucketedData!![5].value, 1.0)
Assert.assertEquals(40.0, iobCobCalculatorPlugin.bucketedData!![6].value, 1.0)
//bucketed data should be null if no bg data available
iobCobCalculatorPlugin.bgReadings = null
iobCobCalculatorPlugin.bgReadings = ArrayList()
iobCobCalculatorPlugin.createBucketedData()
Assert.assertEquals(null, iobCobCalculatorPlugin.bucketedData)
@ -246,11 +244,11 @@ class IobCobCalculatorPluginTest : TestBase() {
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = DateUtil.fromISODateString("2018-09-05T03:50:03Z").time, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = DateUtil.fromISODateString("2018-09-05T03:44:57Z").time, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
iobCobCalculatorPlugin.bgReadings = bgReadingList
iobCobCalculatorPlugin.referenceTime = null
iobCobCalculatorPlugin.referenceTime = -1
Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData)
iobCobCalculatorPlugin.createBucketedData()
Assert.assertEquals(DateUtil.fromISODateString("2018-09-05T13:34:57Z").time, iobCobCalculatorPlugin.bucketedData[0].timestamp)
Assert.assertEquals(DateUtil.fromISODateString("2018-09-05T03:44:57Z").time, iobCobCalculatorPlugin.bucketedData[iobCobCalculatorPlugin.bucketedData.size - 1].timestamp)
Assert.assertEquals(DateUtil.fromISODateString("2018-09-05T13:34:57Z").time, iobCobCalculatorPlugin.bucketedData!![0].timestamp)
Assert.assertEquals(DateUtil.fromISODateString("2018-09-05T03:44:57Z").time, iobCobCalculatorPlugin.bucketedData!![iobCobCalculatorPlugin.bucketedData!!.size - 1].timestamp)
// 5min 4sec data
bgReadingList.clear()

View file

@ -11,6 +11,7 @@ import info.nightscout.androidaps.interfaces.ActivePluginProvider
import info.nightscout.androidaps.interfaces.Constraint
import info.nightscout.androidaps.interfaces.PumpDescription
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.general.maintenance.LoggerUtils
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
import info.nightscout.androidaps.queue.commands.Command
@ -30,7 +31,9 @@ import org.powermock.modules.junit4.PowerMockRunner
import java.util.*
@RunWith(PowerMockRunner::class)
@PrepareForTest(ConstraintChecker::class, VirtualPumpPlugin::class, ToastUtils::class, Context::class, TreatmentsPlugin::class, FabricPrivacy::class)
@PrepareForTest(
ConstraintChecker::class, VirtualPumpPlugin::class, ToastUtils::class, Context::class,
TreatmentsPlugin::class, FabricPrivacy::class, LoggerUtils::class)
class CommandQueueTest : TestBaseWithProfile() {
@Mock lateinit var constraintChecker: ConstraintChecker
@ -39,8 +42,7 @@ class CommandQueueTest : TestBaseWithProfile() {
@Mock lateinit var context: Context
@Mock lateinit var virtualPumpPlugin: VirtualPumpPlugin
@Mock lateinit var sp: SP
private val buildHelper = BuildHelper(Config())
@Mock lateinit var loggerUtils: LoggerUtils
val injector = HasAndroidInjector {
AndroidInjector {
@ -55,7 +57,7 @@ class CommandQueueTest : TestBaseWithProfile() {
@Before
fun prepare() {
commandQueue = CommandQueue(injector, aapsLogger, rxBus, aapsSchedulers, resourceHelper, constraintChecker, profileFunction, lazyActivePlugin, context, sp, buildHelper, fabricPrivacy)
commandQueue = CommandQueue(injector, aapsLogger, rxBus, aapsSchedulers, resourceHelper, constraintChecker, profileFunction, lazyActivePlugin, context, sp, BuildHelper(Config(), loggerUtils), fabricPrivacy)
val pumpDescription = PumpDescription()
pumpDescription.basalMinimumRate = 0.1
@ -65,7 +67,7 @@ class CommandQueueTest : TestBaseWithProfile() {
`when`(virtualPumpPlugin.pumpDescription).thenReturn(pumpDescription)
`when`(virtualPumpPlugin.isThisProfileSet(anyObject())).thenReturn(false)
`when`(activePlugin.activeTreatments).thenReturn(treatmentsPlugin)
`when`(treatmentsPlugin.lastBolusTime).thenReturn(Date(100, 0, 1).time)
`when`(treatmentsPlugin.lastBolusTime).thenReturn(Calendar.getInstance().also { it.set(2000, 0, 1) }.timeInMillis)
`when`(profileFunction.getProfile()).thenReturn(validProfile)
val bolusConstraint = Constraint(0.0)

View file

@ -31,10 +31,10 @@ import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
@RunWith(PowerMockRunner::class)
@PrepareForTest(ConstraintChecker::class, VirtualPumpPlugin::class)
@PrepareForTest(ConstraintChecker::class, VirtualPumpPlugin::class, IobCobCalculatorPlugin::class)
class BolusWizardTest : TestBase() {
private val PUMP_BOLUS_STEP = 0.1
private val pumpBolusStep = 0.1
@Mock lateinit var resourceHelper: ResourceHelper
@Mock lateinit var profileFunction: ProfileFunction
@ -67,6 +67,7 @@ class BolusWizardTest : TestBase() {
}
}
@Suppress("SameParameterValue")
private fun setupProfile(targetLow: Double, targetHigh: Double, insulinSensitivityFactor: Double, insulinToCarbRatio: Double): Profile {
val profile = Mockito.mock(Profile::class.java)
`when`(profile.targetLowMgdl).thenReturn(targetLow)
@ -81,7 +82,7 @@ class BolusWizardTest : TestBase() {
`when`(treatmentsPlugin.lastCalculationTempBasals).thenReturn(IobTotal(System.currentTimeMillis()))
`when`(activePlugin.activePump).thenReturn(virtualPumpPlugin)
val pumpDescription = PumpDescription()
pumpDescription.bolusStep = PUMP_BOLUS_STEP
pumpDescription.bolusStep = pumpBolusStep
`when`(virtualPumpPlugin.pumpDescription).thenReturn(pumpDescription)
Mockito.doAnswer { invocation: InvocationOnMock ->
@ -94,9 +95,9 @@ class BolusWizardTest : TestBase() {
/** Should calculate the same bolus when different blood glucose but both in target range */
fun shouldCalculateTheSameBolusWhenBGsInRange() {
val profile = setupProfile(4.0, 8.0, 20.0, 12.0)
var bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 4.2, 0.0, 100.0, true, true, true, true, false, false, false, false)
var bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 4.2, 0.0, 100.0, useBg = true, useCob = true, includeBolusIOB = true, includeBasalIOB = true, useSuperBolus = false, useTT = false, useTrend = false, useAlarm = false)
val bolusForBg42 = bw.calculatedTotalInsulin
bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 5.4, 0.0, 100.0, true, true, true, true, false, false, false, false)
bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 5.4, 0.0, 100.0, useBg = true, useCob = true, includeBolusIOB = true, includeBasalIOB = true, useSuperBolus = false, useTT = false, useTrend = false, useAlarm = false)
val bolusForBg54 = bw.calculatedTotalInsulin
Assert.assertEquals(bolusForBg42, bolusForBg54, 0.01)
}
@ -104,9 +105,9 @@ class BolusWizardTest : TestBase() {
@Test
fun shouldCalculateHigherBolusWhenHighBG() {
val profile = setupProfile(4.0, 8.0, 20.0, 12.0)
var bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 9.8, 0.0, 100.0, true, true, true, true, false, false, false, false)
var bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 9.8, 0.0, 100.0, useBg = true, useCob = true, includeBolusIOB = true, includeBasalIOB = true, useSuperBolus = false, useTT = false, useTrend = false, useAlarm = false)
val bolusForHighBg = bw.calculatedTotalInsulin
bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 5.4, 0.0, 100.0, true, true, true, true, false, false, false, false)
bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 5.4, 0.0, 100.0, useBg = true, useCob = true, includeBolusIOB = true, includeBasalIOB = true, useSuperBolus = false, useTT = false, useTrend = false, useAlarm = false)
val bolusForBgInRange = bw.calculatedTotalInsulin
Assert.assertTrue(bolusForHighBg > bolusForBgInRange)
}
@ -114,9 +115,9 @@ class BolusWizardTest : TestBase() {
@Test
fun shouldCalculateLowerBolusWhenLowBG() {
val profile = setupProfile(4.0, 8.0, 20.0, 12.0)
var bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 3.6, 0.0, 100.0, true, true, true, true, false, false, false, false)
var bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 3.6, 0.0, 100.0, useBg = true, useCob = true, includeBolusIOB = true, includeBasalIOB = true, useSuperBolus = false, useTT = false, useTrend = false, useAlarm = false)
val bolusForLowBg = bw.calculatedTotalInsulin
bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 5.4, 0.0, 100.0, true, true, true, true, false, false, false, false)
bw = BolusWizard(injector).doCalc(profile, "", null, 20, 0.0, 5.4, 0.0, 100.0, useBg = true, useCob = true, includeBolusIOB = true, includeBasalIOB = true, useSuperBolus = false, useTT = false, useTrend = false, useAlarm = false)
val bolusForBgInRange = bw.calculatedTotalInsulin
Assert.assertTrue(bolusForLowBg < bolusForBgInRange)
}

View file

@ -12,7 +12,7 @@ buildscript {
dagger_version = '2.32'
coroutinesVersion = '1.4.1'
activityVersion = '1.2.0'
fragmentktx_version = '1.3.0-rc02'
fragmentktx_version = '1.3.0'
ormLiteVersion = '4.46'
nav_version = '2.3.3'
appcompat_version = '1.2.0'

View file

@ -46,9 +46,9 @@ public class Constants {
public static final int defaultEatingSoonTTDuration = 45; // min
public static final double defaultEatingSoonTTmgdl = 90d;
public static final double defaultEatingSoonTTmmol = 5d;
public static final int defaultHypoTTDuration = 30; // min
public static final double defaultHypoTTmgdl = 120d;
public static final double defaultHypoTTmmol = 6.5d;
public static final int defaultHypoTTDuration = 60; // min
public static final double defaultHypoTTmgdl = 160d;
public static final double defaultHypoTTmmol = 8d;
public static final double MIN_TT_MGDL = 72d;
public static final double MAX_TT_MGDL = 180d;

View file

@ -7,78 +7,20 @@ import info.nightscout.androidaps.data.Profile
*/
interface ConstraintsInterface {
@JvmDefault
fun isLoopInvocationAllowed(value: Constraint<Boolean>): Constraint<Boolean> {
return value
}
@JvmDefault
fun isClosedLoopAllowed(value: Constraint<Boolean>): Constraint<Boolean> {
return value
}
@JvmDefault
fun isAutosensModeEnabled(value: Constraint<Boolean>): Constraint<Boolean> {
return value
}
@JvmDefault
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
}
@JvmDefault fun isLoopInvocationAllowed(value: Constraint<Boolean>): Constraint<Boolean> = value
@JvmDefault fun isClosedLoopAllowed(value: Constraint<Boolean>): Constraint<Boolean> = 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 fun isSMBModeEnabled(value: Constraint<Boolean>): Constraint<Boolean> = value
@JvmDefault fun isUAMEnabled(value: Constraint<Boolean>): Constraint<Boolean> = 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 fun applyBasalPercentConstraints(percentRate: Constraint<Int>, profile: Profile): Constraint<Int> = percentRate
@JvmDefault fun applyBolusConstraints(insulin: Constraint<Double>): Constraint<Double> = insulin
@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 fun isAutomationEnabled(value: Constraint<Boolean>): Constraint<Boolean> = value
}