IobCobCalculatorPlugin -> kt
This commit is contained in:
parent
bbcc813474
commit
6f3266e845
35 changed files with 1137 additions and 1348 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -2,13 +2,16 @@ 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).
|
||||
*/
|
||||
object LoggerUtils {
|
||||
@Singleton
|
||||
class LoggerUtils @Inject constructor() {
|
||||
|
||||
var SUFFIX = ".log.zip"
|
||||
var suffix = ".log.zip"
|
||||
|
||||
/**
|
||||
* Returns the directory, in which the logs are stored on the system. This is configured in the
|
||||
|
|
|
@ -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.logDirectory
|
||||
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,7 +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() {
|
||||
val logDir = File(LoggerUtils.logDirectory)
|
||||
val logDir = File(loggerUtils.logDirectory)
|
||||
val files = logDir.listFiles { _: File?, name: String ->
|
||||
(name.startsWith("AndroidAPS") && name.endsWith(".zip"))
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ class MaintenancePlugin @Inject constructor(
|
|||
file.delete()
|
||||
}
|
||||
}
|
||||
val exportDir = File(LoggerUtils.logDirectory, "exports")
|
||||
val exportDir = File(loggerUtils.logDirectory, "exports")
|
||||
if (exportDir.exists()) {
|
||||
val expFiles = exportDir.listFiles()
|
||||
for (file in expFiles) {
|
||||
|
@ -96,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)
|
||||
|
@ -137,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>) {
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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 += " " +
|
||||
|
|
|
@ -322,7 +322,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
|
|||
} 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("avgDelta", deltastring(glucoseStatus.avgDelta, glucoseStatus.avgDelta * Constants.MGDL_TO_MMOLL, units));
|
||||
}
|
||||
dataMap.putLong("sgvLevel", sgvLevel);
|
||||
dataMap.putDouble("sgvDouble", lastBG.getValue());
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
package info.nightscout.androidaps.plugins.iob.iobCobCalculator;
|
||||
|
||||
/**
|
||||
* Created by mike on 10.06.2017.
|
||||
*/
|
||||
|
||||
public class BasalData {
|
||||
public double basal;
|
||||
public double tempBasalAbsolute;
|
||||
public boolean isTempBasalRunning;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package info.nightscout.androidaps.plugins.iob.iobCobCalculator
|
||||
|
||||
class BasalData {
|
||||
|
||||
var basal = 0.0
|
||||
var tempBasalAbsolute = 0.0
|
||||
var isTempBasalRunning = false
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package info.nightscout.androidaps.plugins.iob.iobCobCalculator;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import info.nightscout.androidaps.utils.DecimalFormatter;
|
||||
|
||||
public class CobInfo {
|
||||
/** All COB up to now, including carbs not yet processed by IobCob calculation. */
|
||||
@Nullable
|
||||
public final Double displayCob;
|
||||
public final double futureCarbs;
|
||||
|
||||
public CobInfo(@Nullable Double displayCob, double futureCarbs) {
|
||||
this.displayCob = displayCob;
|
||||
this.futureCarbs = futureCarbs;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String generateCOBString() {
|
||||
String cobStringResult = "--g";
|
||||
if (displayCob != null) {
|
||||
cobStringResult = DecimalFormatter.to0Decimal(displayCob);
|
||||
if (futureCarbs > 0) {
|
||||
cobStringResult += "(" + DecimalFormatter.to0Decimal(futureCarbs) + ")";
|
||||
}
|
||||
cobStringResult += "g";
|
||||
}
|
||||
return cobStringResult;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package info.nightscout.androidaps.plugins.iob.iobCobCalculator
|
||||
|
||||
import info.nightscout.androidaps.utils.DecimalFormatter
|
||||
|
||||
/** All COB up to now, including carbs not yet processed by IobCob calculation. */
|
||||
class CobInfo(val displayCob: Double?, val futureCarbs: Double) {
|
||||
|
||||
fun generateCOBString(): String {
|
||||
var cobStringResult = "--g"
|
||||
if (displayCob != null) {
|
||||
cobStringResult = DecimalFormatter.to0Decimal(displayCob)
|
||||
if (futureCarbs > 0)
|
||||
cobStringResult += "(${DecimalFormatter.to0Decimal(futureCarbs)})"
|
||||
cobStringResult += "g"
|
||||
}
|
||||
return cobStringResult
|
||||
}
|
||||
}
|
|
@ -1,186 +0,0 @@
|
|||
package info.nightscout.androidaps.plugins.iob.iobCobCalculator;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.android.HasAndroidInjector;
|
||||
import info.nightscout.androidaps.database.entities.GlucoseValue;
|
||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
||||
import info.nightscout.androidaps.logging.LTag;
|
||||
import info.nightscout.androidaps.utils.DateUtil;
|
||||
import info.nightscout.androidaps.utils.DecimalFormatter;
|
||||
import info.nightscout.androidaps.utils.Round;
|
||||
|
||||
/**
|
||||
* Created by mike on 04.01.2017.
|
||||
*/
|
||||
|
||||
public class GlucoseStatus {
|
||||
@Inject public AAPSLogger aapsLogger;
|
||||
@Inject public IobCobCalculatorPlugin iobCobCalculatorPlugin;
|
||||
|
||||
private final HasAndroidInjector injector;
|
||||
|
||||
public double glucose = 0d;
|
||||
public double noise = 0d;
|
||||
public double delta = 0d;
|
||||
public double avgdelta = 0d;
|
||||
public double short_avgdelta = 0d;
|
||||
public double long_avgdelta = 0d;
|
||||
public long date = 0L;
|
||||
|
||||
|
||||
public String log() {
|
||||
return "Glucose: " + DecimalFormatter.to0Decimal(glucose) + " mg/dl " +
|
||||
"Noise: " + DecimalFormatter.to0Decimal(noise) + " " +
|
||||
"Delta: " + DecimalFormatter.to0Decimal(delta) + " mg/dl" +
|
||||
"Short avg. delta: " + " " + DecimalFormatter.to2Decimal(short_avgdelta) + " mg/dl " +
|
||||
"Long avg. delta: " + DecimalFormatter.to2Decimal(long_avgdelta) + " mg/dl";
|
||||
}
|
||||
|
||||
public GlucoseStatus(HasAndroidInjector injector) {
|
||||
injector.androidInjector().inject(this);
|
||||
this.injector = injector;
|
||||
}
|
||||
|
||||
public GlucoseStatus round() {
|
||||
this.glucose = Round.roundTo(this.glucose, 0.1);
|
||||
this.noise = Round.roundTo(this.noise, 0.01);
|
||||
this.delta = Round.roundTo(this.delta, 0.01);
|
||||
this.avgdelta = Round.roundTo(this.avgdelta, 0.01);
|
||||
this.short_avgdelta = Round.roundTo(this.short_avgdelta, 0.01);
|
||||
this.long_avgdelta = Round.roundTo(this.long_avgdelta, 0.01);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
public GlucoseStatus getGlucoseStatusData() {
|
||||
return getGlucoseStatusData(false);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public GlucoseStatus getGlucoseStatusData(boolean allowOldData) {
|
||||
// load 45min
|
||||
//long fromtime = DateUtil.now() - 60 * 1000L * 45;
|
||||
//List<BgReading> data = MainApp.getDbHelper().getBgreadingsDataFromTime(fromtime, false);
|
||||
|
||||
synchronized (iobCobCalculatorPlugin.getDataLock()) {
|
||||
|
||||
List<GlucoseValue> data = iobCobCalculatorPlugin.getBgReadings();
|
||||
|
||||
if (data == null) {
|
||||
aapsLogger.debug(LTag.GLUCOSE, "data=null");
|
||||
return null;
|
||||
}
|
||||
|
||||
int sizeRecords = data.size();
|
||||
if (sizeRecords == 0) {
|
||||
aapsLogger.debug(LTag.GLUCOSE, "sizeRecords==0");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data.get(0).getTimestamp() < DateUtil.now() - 7 * 60 * 1000L && !allowOldData) {
|
||||
aapsLogger.debug(LTag.GLUCOSE, "olddata");
|
||||
return null;
|
||||
}
|
||||
|
||||
GlucoseValue now = data.get(0);
|
||||
long now_date = now.getTimestamp();
|
||||
double change;
|
||||
|
||||
if (sizeRecords == 1) {
|
||||
GlucoseStatus status = new GlucoseStatus(injector);
|
||||
status.glucose = now.getValue();
|
||||
status.noise = 0d;
|
||||
status.short_avgdelta = 0d;
|
||||
status.delta = 0d;
|
||||
status.long_avgdelta = 0d;
|
||||
status.avgdelta = 0d; // for OpenAPS MA
|
||||
status.date = now_date;
|
||||
aapsLogger.debug(LTag.GLUCOSE, "sizeRecords==1");
|
||||
return status.round();
|
||||
}
|
||||
|
||||
ArrayList<Double> now_value_list = new ArrayList<>();
|
||||
ArrayList<Double> last_deltas = new ArrayList<>();
|
||||
ArrayList<Double> short_deltas = new ArrayList<>();
|
||||
ArrayList<Double> long_deltas = new ArrayList<>();
|
||||
|
||||
// Use the latest sgv value in the now calculations
|
||||
now_value_list.add(now.getValue());
|
||||
|
||||
for (int i = 1; i < sizeRecords; i++) {
|
||||
if (data.get(i).getValue() > 38) {
|
||||
GlucoseValue then = data.get(i);
|
||||
long then_date = then.getTimestamp();
|
||||
double avgdelta;
|
||||
long minutesago;
|
||||
|
||||
minutesago = Math.round((now_date - then_date) / (1000d * 60));
|
||||
// multiply by 5 to get the same units as delta, i.e. mg/dL/5m
|
||||
change = now.getValue() - then.getValue();
|
||||
avgdelta = change / minutesago * 5;
|
||||
|
||||
aapsLogger.debug(LTag.GLUCOSE, then.toString() + " minutesago=" + minutesago + " avgdelta=" + avgdelta);
|
||||
|
||||
// use the average of all data points in the last 2.5m for all further "now" calculations
|
||||
if (0 < minutesago && minutesago < 2.5) {
|
||||
// Keep and average all values within the last 2.5 minutes
|
||||
now_value_list.add(then.getValue());
|
||||
now.setValue(average(now_value_list));
|
||||
// short_deltas are calculated from everything ~5-15 minutes ago
|
||||
} else if (2.5 < minutesago && minutesago < 17.5) {
|
||||
//console.error(minutesago, avgdelta);
|
||||
short_deltas.add(avgdelta);
|
||||
// last_deltas are calculated from everything ~5 minutes ago
|
||||
if (2.5 < minutesago && minutesago < 7.5) {
|
||||
last_deltas.add(avgdelta);
|
||||
}
|
||||
// long_deltas are calculated from everything ~20-40 minutes ago
|
||||
} else if (17.5 < minutesago && minutesago < 42.5) {
|
||||
long_deltas.add(avgdelta);
|
||||
} else {
|
||||
// Do not process any more records after >= 42.5 minutes
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GlucoseStatus status = new GlucoseStatus(injector);
|
||||
status.glucose = now.getValue();
|
||||
status.date = now_date;
|
||||
status.noise = 0d; //for now set to nothing as not all CGMs report noise
|
||||
|
||||
status.short_avgdelta = average(short_deltas);
|
||||
|
||||
if (last_deltas.isEmpty()) {
|
||||
status.delta = status.short_avgdelta;
|
||||
} else {
|
||||
status.delta = average(last_deltas);
|
||||
}
|
||||
|
||||
status.long_avgdelta = average(long_deltas);
|
||||
status.avgdelta = status.short_avgdelta; // for OpenAPS MA
|
||||
|
||||
aapsLogger.debug(LTag.GLUCOSE, status.log());
|
||||
return status.round();
|
||||
}
|
||||
}
|
||||
|
||||
public static double average(ArrayList<Double> array) {
|
||||
double sum = 0d;
|
||||
|
||||
if (array.size() == 0)
|
||||
return 0d;
|
||||
|
||||
for (Double value : array) {
|
||||
sum += value;
|
||||
}
|
||||
return sum / array.size();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
package info.nightscout.androidaps.plugins.iob.iobCobCalculator
|
||||
|
||||
import dagger.android.HasAndroidInjector
|
||||
import info.nightscout.androidaps.logging.AAPSLogger
|
||||
import info.nightscout.androidaps.logging.LTag
|
||||
import info.nightscout.androidaps.utils.DateUtil
|
||||
import info.nightscout.androidaps.utils.DecimalFormatter
|
||||
import info.nightscout.androidaps.utils.Round
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
class GlucoseStatus(private val injector: HasAndroidInjector) {
|
||||
|
||||
@Inject lateinit var aapsLogger: AAPSLogger
|
||||
@Inject lateinit var iobCobCalculatorPlugin: IobCobCalculatorPlugin
|
||||
|
||||
var glucose = 0.0
|
||||
var noise = 0.0
|
||||
var delta = 0.0
|
||||
var avgDelta = 0.0
|
||||
var shortAvgDelta = 0.0
|
||||
var longAvgDelta = 0.0
|
||||
var date = 0L
|
||||
|
||||
init {
|
||||
injector.androidInjector().inject(this)
|
||||
}
|
||||
|
||||
fun log(): String {
|
||||
return "Glucose: " + DecimalFormatter.to0Decimal(glucose) + " mg/dl " +
|
||||
"Noise: " + DecimalFormatter.to0Decimal(noise) + " " +
|
||||
"Delta: " + DecimalFormatter.to0Decimal(delta) + " mg/dl" +
|
||||
"Short avg. delta: " + " " + DecimalFormatter.to2Decimal(shortAvgDelta) + " mg/dl " +
|
||||
"Long avg. delta: " + DecimalFormatter.to2Decimal(longAvgDelta) + " mg/dl"
|
||||
}
|
||||
|
||||
fun round(): GlucoseStatus {
|
||||
glucose = Round.roundTo(glucose, 0.1)
|
||||
noise = Round.roundTo(noise, 0.01)
|
||||
delta = Round.roundTo(delta, 0.01)
|
||||
avgDelta = Round.roundTo(avgDelta, 0.01)
|
||||
shortAvgDelta = Round.roundTo(shortAvgDelta, 0.01)
|
||||
longAvgDelta = Round.roundTo(longAvgDelta, 0.01)
|
||||
return this
|
||||
}
|
||||
|
||||
val glucoseStatusData: GlucoseStatus?
|
||||
get() = getGlucoseStatusData(false)
|
||||
|
||||
fun getGlucoseStatusData(allowOldData: Boolean): GlucoseStatus? {
|
||||
synchronized(iobCobCalculatorPlugin.dataLock) {
|
||||
val data = iobCobCalculatorPlugin.bgReadings
|
||||
val sizeRecords = data.size
|
||||
if (sizeRecords == 0) {
|
||||
aapsLogger.debug(LTag.GLUCOSE, "sizeRecords==0")
|
||||
return null
|
||||
}
|
||||
if (data[0].timestamp < DateUtil.now() - 7 * 60 * 1000L && !allowOldData) {
|
||||
aapsLogger.debug(LTag.GLUCOSE, "oldData")
|
||||
return null
|
||||
}
|
||||
val now = data[0]
|
||||
val nowDate = now.timestamp
|
||||
var change: Double
|
||||
if (sizeRecords == 1) {
|
||||
val status = GlucoseStatus(injector)
|
||||
status.glucose = now.value
|
||||
status.noise = 0.0
|
||||
status.shortAvgDelta = 0.0
|
||||
status.delta = 0.0
|
||||
status.longAvgDelta = 0.0
|
||||
status.avgDelta = 0.0 // for OpenAPS MA
|
||||
status.date = nowDate
|
||||
aapsLogger.debug(LTag.GLUCOSE, "sizeRecords==1")
|
||||
return status.round()
|
||||
}
|
||||
val nowValueList = ArrayList<Double>()
|
||||
val lastDeltas = ArrayList<Double>()
|
||||
val shortDeltas = ArrayList<Double>()
|
||||
val longDeltas = ArrayList<Double>()
|
||||
|
||||
// Use the latest sgv value in the now calculations
|
||||
nowValueList.add(now.value)
|
||||
for (i in 1 until sizeRecords) {
|
||||
if (data[i].value > 38) {
|
||||
val then = data[i]
|
||||
val thenDate = then.timestamp
|
||||
|
||||
val minutesAgo = ((nowDate - thenDate) / (1000.0 * 60)).roundToLong()
|
||||
// multiply by 5 to get the same units as delta, i.e. mg/dL/5m
|
||||
change = now.value - then.value
|
||||
val avgDel = change / minutesAgo * 5
|
||||
aapsLogger.debug(LTag.GLUCOSE, "$then minutesAgo=$minutesAgo avgDelta=$avgDel")
|
||||
|
||||
// use the average of all data points in the last 2.5m for all further "now" calculations
|
||||
if (0 < minutesAgo && minutesAgo < 2.5) {
|
||||
// Keep and average all values within the last 2.5 minutes
|
||||
nowValueList.add(then.value)
|
||||
now.value = average(nowValueList)
|
||||
// short_deltas are calculated from everything ~5-15 minutes ago
|
||||
} else if (2.5 < minutesAgo && minutesAgo < 17.5) {
|
||||
//console.error(minutesAgo, avgDelta);
|
||||
shortDeltas.add(avgDel)
|
||||
// last_deltas are calculated from everything ~5 minutes ago
|
||||
if (2.5 < minutesAgo && minutesAgo < 7.5) {
|
||||
lastDeltas.add(avgDel)
|
||||
}
|
||||
// long_deltas are calculated from everything ~20-40 minutes ago
|
||||
} else if (17.5 < minutesAgo && minutesAgo < 42.5) {
|
||||
longDeltas.add(avgDel)
|
||||
} else {
|
||||
// Do not process any more records after >= 42.5 minutes
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
val status = GlucoseStatus(injector)
|
||||
status.glucose = now.value
|
||||
status.date = nowDate
|
||||
status.noise = 0.0 //for now set to nothing as not all CGMs report noise
|
||||
status.shortAvgDelta = average(shortDeltas)
|
||||
if (lastDeltas.isEmpty()) {
|
||||
status.delta = status.shortAvgDelta
|
||||
} else {
|
||||
status.delta = average(lastDeltas)
|
||||
}
|
||||
status.longAvgDelta = average(longDeltas)
|
||||
status.avgDelta = status.shortAvgDelta // for OpenAPS MA
|
||||
aapsLogger.debug(LTag.GLUCOSE, status.log())
|
||||
return status.round()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun average(array: ArrayList<Double>): Double {
|
||||
var sum = 0.0
|
||||
if (array.size == 0) return 0.0
|
||||
for (value in array) {
|
||||
sum += value
|
||||
}
|
||||
return sum / array.size
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,968 +0,0 @@
|
|||
package info.nightscout.androidaps.plugins.iob.iobCobCalculator;
|
||||
|
||||
import android.os.SystemClock;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.collection.LongSparseArray;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.android.HasAndroidInjector;
|
||||
import info.nightscout.androidaps.Constants;
|
||||
import info.nightscout.androidaps.R;
|
||||
import info.nightscout.androidaps.data.IobTotal;
|
||||
import info.nightscout.androidaps.data.MealData;
|
||||
import info.nightscout.androidaps.data.Profile;
|
||||
import info.nightscout.androidaps.database.AppRepository;
|
||||
import info.nightscout.androidaps.database.entities.GlucoseValue;
|
||||
import info.nightscout.androidaps.db.TemporaryBasal;
|
||||
import info.nightscout.androidaps.db.Treatment;
|
||||
import info.nightscout.androidaps.events.Event;
|
||||
import info.nightscout.androidaps.events.EventAppInitialized;
|
||||
import info.nightscout.androidaps.events.EventConfigBuilderChange;
|
||||
import info.nightscout.androidaps.events.EventNewBG;
|
||||
import info.nightscout.androidaps.events.EventNewBasalProfile;
|
||||
import info.nightscout.androidaps.events.EventPreferenceChange;
|
||||
import info.nightscout.androidaps.interfaces.ActivePluginProvider;
|
||||
import info.nightscout.androidaps.interfaces.IobCobCalculatorInterface;
|
||||
import info.nightscout.androidaps.interfaces.PluginBase;
|
||||
import info.nightscout.androidaps.interfaces.PluginDescription;
|
||||
import info.nightscout.androidaps.interfaces.PluginType;
|
||||
import info.nightscout.androidaps.interfaces.ProfileFunction;
|
||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
||||
import info.nightscout.androidaps.logging.LTag;
|
||||
import info.nightscout.androidaps.plugins.bus.RxBusWrapper;
|
||||
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData;
|
||||
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryBgData;
|
||||
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData;
|
||||
import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin;
|
||||
import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin;
|
||||
import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin;
|
||||
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
|
||||
import info.nightscout.androidaps.utils.DateUtil;
|
||||
import info.nightscout.androidaps.utils.DecimalFormatter;
|
||||
import info.nightscout.androidaps.utils.FabricPrivacy;
|
||||
import info.nightscout.androidaps.utils.T;
|
||||
import info.nightscout.androidaps.utils.resources.ResourceHelper;
|
||||
import info.nightscout.androidaps.utils.rx.AapsSchedulers;
|
||||
import info.nightscout.androidaps.utils.sharedPreferences.SP;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
|
||||
import static info.nightscout.androidaps.utils.DateUtil.now;
|
||||
|
||||
@Singleton
|
||||
public class IobCobCalculatorPlugin extends PluginBase implements IobCobCalculatorInterface {
|
||||
private final HasAndroidInjector injector;
|
||||
private final AapsSchedulers aapsSchedulers;
|
||||
private final SP sp;
|
||||
private final RxBusWrapper rxBus;
|
||||
private final ResourceHelper resourceHelper;
|
||||
private final ProfileFunction profileFunction;
|
||||
private final ActivePluginProvider activePlugin;
|
||||
private final TreatmentsPlugin treatmentsPlugin;
|
||||
private final SensitivityOref1Plugin sensitivityOref1Plugin;
|
||||
private final SensitivityAAPSPlugin sensitivityAAPSPlugin;
|
||||
private final SensitivityWeightedAveragePlugin sensitivityWeightedAveragePlugin;
|
||||
private final FabricPrivacy fabricPrivacy;
|
||||
private final DateUtil dateUtil;
|
||||
private final AppRepository repository;
|
||||
|
||||
private final CompositeDisposable disposable = new CompositeDisposable();
|
||||
|
||||
private LongSparseArray<IobTotal> iobTable = new LongSparseArray<>(); // oldest at index 0
|
||||
private LongSparseArray<IobTotal> absIobTable = new LongSparseArray<>(); // oldest at index 0, absolute insulin in the body
|
||||
private LongSparseArray<AutosensData> autosensDataTable = new LongSparseArray<>(); // oldest at index 0
|
||||
private LongSparseArray<BasalData> basalDataTable = new LongSparseArray<>(); // oldest at index 0
|
||||
|
||||
private volatile List<GlucoseValue> bgReadings = null; // newest at index 0
|
||||
private volatile List<InMemoryGlucoseValue> bucketed_data = null;
|
||||
|
||||
// we need to make sure that bucketed_data will always have the same timestamp for correct use of cached values
|
||||
// once referenceTime != null all bucketed data should be (x * 5min) from referenceTime
|
||||
Long referenceTime = null;
|
||||
private Boolean lastUsed5minCalculation = null; // true if used 5min bucketed data
|
||||
|
||||
private final Object dataLock = new Object();
|
||||
|
||||
boolean stopCalculationTrigger = false;
|
||||
private Thread thread = null;
|
||||
|
||||
@Inject
|
||||
public IobCobCalculatorPlugin(
|
||||
HasAndroidInjector injector,
|
||||
AAPSLogger aapsLogger,
|
||||
AapsSchedulers aapsSchedulers,
|
||||
RxBusWrapper rxBus,
|
||||
SP sp,
|
||||
ResourceHelper resourceHelper,
|
||||
ProfileFunction profileFunction,
|
||||
ActivePluginProvider activePlugin,
|
||||
TreatmentsPlugin treatmentsPlugin,
|
||||
SensitivityOref1Plugin sensitivityOref1Plugin,
|
||||
SensitivityAAPSPlugin sensitivityAAPSPlugin,
|
||||
SensitivityWeightedAveragePlugin sensitivityWeightedAveragePlugin,
|
||||
FabricPrivacy fabricPrivacy,
|
||||
DateUtil dateUtil,
|
||||
AppRepository repository
|
||||
) {
|
||||
super(new PluginDescription()
|
||||
.mainType(PluginType.GENERAL)
|
||||
.pluginName(R.string.iobcobcalculator)
|
||||
.showInList(false)
|
||||
.neverVisible(true)
|
||||
.alwaysEnabled(true),
|
||||
aapsLogger, resourceHelper, injector
|
||||
);
|
||||
this.injector = injector;
|
||||
this.aapsSchedulers = aapsSchedulers;
|
||||
this.sp = sp;
|
||||
this.rxBus = rxBus;
|
||||
this.resourceHelper = resourceHelper;
|
||||
this.profileFunction = profileFunction;
|
||||
this.activePlugin = activePlugin;
|
||||
this.treatmentsPlugin = treatmentsPlugin;
|
||||
this.sensitivityOref1Plugin = sensitivityOref1Plugin;
|
||||
this.sensitivityAAPSPlugin = sensitivityAAPSPlugin;
|
||||
this.sensitivityWeightedAveragePlugin = sensitivityWeightedAveragePlugin;
|
||||
this.fabricPrivacy = fabricPrivacy;
|
||||
this.dateUtil = dateUtil;
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
// EventConfigBuilderChange
|
||||
disposable.add(rxBus
|
||||
.toObservable(EventConfigBuilderChange.class)
|
||||
.observeOn(aapsSchedulers.getIo())
|
||||
.subscribe(event -> {
|
||||
stopCalculation("onEventConfigBuilderChange");
|
||||
synchronized (dataLock) {
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "Invalidating cached data because of configuration change.");
|
||||
resetData();
|
||||
}
|
||||
runCalculation("onEventConfigBuilderChange", System.currentTimeMillis(), false, true, event);
|
||||
}, fabricPrivacy::logException)
|
||||
);
|
||||
// EventNewBasalProfile
|
||||
disposable.add(rxBus
|
||||
.toObservable(EventNewBasalProfile.class)
|
||||
.observeOn(aapsSchedulers.getIo())
|
||||
.subscribe(event -> {
|
||||
if (event == null) { // on init no need of reset
|
||||
return;
|
||||
}
|
||||
stopCalculation("onNewProfile");
|
||||
synchronized (dataLock) {
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "Invalidating cached data because of new profile.");
|
||||
resetData();
|
||||
}
|
||||
runCalculation("onNewProfile", System.currentTimeMillis(), false, true, event);
|
||||
}, fabricPrivacy::logException)
|
||||
);
|
||||
// EventNewBG .... cannot be used for invalidating because only event with last BG is fired
|
||||
disposable.add(rxBus
|
||||
.toObservable(EventNewBG.class)
|
||||
.observeOn(aapsSchedulers.getIo())
|
||||
.debounce(1L, TimeUnit.SECONDS)
|
||||
.subscribe(event -> {
|
||||
stopCalculation("onEventNewBG");
|
||||
runCalculation("onEventNewBG", System.currentTimeMillis(), true, true, event);
|
||||
}, fabricPrivacy::logException)
|
||||
);
|
||||
// EventPreferenceChange
|
||||
disposable.add(rxBus
|
||||
.toObservable(EventPreferenceChange.class)
|
||||
.observeOn(aapsSchedulers.getIo())
|
||||
.subscribe(event -> {
|
||||
if (event.isChanged(resourceHelper, R.string.key_openapsama_autosens_period) ||
|
||||
event.isChanged(resourceHelper, R.string.key_age) ||
|
||||
event.isChanged(resourceHelper, R.string.key_absorption_maxtime) ||
|
||||
event.isChanged(resourceHelper, R.string.key_openapsama_min_5m_carbimpact) ||
|
||||
event.isChanged(resourceHelper, R.string.key_absorption_cutoff) ||
|
||||
event.isChanged(resourceHelper, R.string.key_openapsama_autosens_max) ||
|
||||
event.isChanged(resourceHelper, R.string.key_openapsama_autosens_min) ||
|
||||
event.isChanged(resourceHelper, R.string.key_insulin_oref_peak)
|
||||
) {
|
||||
stopCalculation("onEventPreferenceChange");
|
||||
synchronized (dataLock) {
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "Invalidating cached data because of preference change.");
|
||||
resetData();
|
||||
}
|
||||
runCalculation("onEventPreferenceChange", System.currentTimeMillis(), false, true, event);
|
||||
}
|
||||
}, fabricPrivacy::logException)
|
||||
);
|
||||
// EventAppInitialized
|
||||
disposable.add(rxBus
|
||||
.toObservable(EventAppInitialized.class)
|
||||
.observeOn(aapsSchedulers.getIo())
|
||||
.subscribe(event -> runCalculation("onEventAppInitialized", System.currentTimeMillis(), true, true, event), fabricPrivacy::logException)
|
||||
);
|
||||
// EventNewHistoryData
|
||||
disposable.add(rxBus
|
||||
.toObservable(EventNewHistoryData.class)
|
||||
.observeOn(aapsSchedulers.getIo())
|
||||
.subscribe(event -> newHistoryData(event, false), fabricPrivacy::logException)
|
||||
);
|
||||
// EventNewHistoryBgData
|
||||
disposable.add(rxBus
|
||||
.toObservable(EventNewHistoryBgData.class)
|
||||
.observeOn(aapsSchedulers.getIo())
|
||||
.subscribe(event -> newHistoryData(new EventNewHistoryData(event.getTimestamp()), true), fabricPrivacy::logException)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
disposable.clear();
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
public LongSparseArray<AutosensData> getAutosensDataTable() {
|
||||
return autosensDataTable;
|
||||
}
|
||||
|
||||
public List<GlucoseValue> getBgReadings() {
|
||||
return bgReadings;
|
||||
}
|
||||
|
||||
public void setBgReadings(List<GlucoseValue> bgReadings) {
|
||||
this.bgReadings = bgReadings;
|
||||
}
|
||||
|
||||
public List<InMemoryGlucoseValue> getBucketedData() {
|
||||
return bucketed_data;
|
||||
}
|
||||
|
||||
public Object getDataLock() {
|
||||
return dataLock;
|
||||
}
|
||||
|
||||
// roundup to whole minute
|
||||
public static long roundUpTime(long time) {
|
||||
if (time % 60000 == 0)
|
||||
return time;
|
||||
long rounded = (time / 60000 + 1) * 60000;
|
||||
return rounded;
|
||||
}
|
||||
|
||||
long adjustToReferenceTime(long someTime) {
|
||||
if (referenceTime == null) {
|
||||
referenceTime = someTime;
|
||||
return someTime;
|
||||
}
|
||||
long diff = Math.abs(someTime - referenceTime);
|
||||
diff %= T.mins(5).msecs();
|
||||
if (diff > T.mins(2).plus(T.secs(30)).msecs())
|
||||
diff = diff - T.mins(5).msecs();
|
||||
long newTime = someTime + diff;
|
||||
return newTime;
|
||||
}
|
||||
|
||||
void loadBgData(long to) {
|
||||
Profile profile = profileFunction.getProfile(to);
|
||||
double dia = Constants.defaultDIA;
|
||||
if (profile != null) dia = profile.getDia();
|
||||
long start = to - T.hours((long) (24 + dia)).msecs();
|
||||
if (DateUtil.isCloseToNow(to)) {
|
||||
// if close to now expect there can be some readings with time in close future (caused by wrong time setting)
|
||||
// so read all records
|
||||
bgReadings = repository.compatGetBgReadingsDataFromTime(start, false).blockingGet();
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "BG data loaded. Size: " + bgReadings.size() + " Start date: " + dateUtil.dateAndTimeString(start));
|
||||
} else {
|
||||
bgReadings = repository.compatGetBgReadingsDataFromTime(start, to, false).blockingGet();
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "BG data loaded. Size: " + bgReadings.size() + " Start date: " + dateUtil.dateAndTimeString(start) + " End date: " + dateUtil.dateAndTimeString(to));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAbout5minData() {
|
||||
synchronized (dataLock) {
|
||||
if (bgReadings == null || bgReadings.size() < 3) {
|
||||
return true;
|
||||
}
|
||||
long totalDiff = 0;
|
||||
for (int i = 1; i < bgReadings.size(); ++i) {
|
||||
long bgTime = bgReadings.get(i).getTimestamp();
|
||||
long lastbgTime = bgReadings.get(i - 1).getTimestamp();
|
||||
long diff = lastbgTime - bgTime;
|
||||
diff %= T.mins(5).msecs();
|
||||
if (diff > T.mins(2).plus(T.secs(30)).msecs())
|
||||
diff = diff - T.mins(5).msecs();
|
||||
totalDiff += diff;
|
||||
diff = Math.abs(diff);
|
||||
if (diff > T.secs(30).msecs()) {
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "Interval detection: values: " + bgReadings.size() + " diff: " + (diff / 1000) + "[s] is5minData: " + false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
long averageDiff = totalDiff / bgReadings.size() / 1000;
|
||||
boolean is5mindata = averageDiff < 1;
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "Interval detection: values: " + bgReadings.size() + " averageDiff: " + averageDiff + "[s] is5minData: " + is5mindata);
|
||||
return is5mindata;
|
||||
}
|
||||
}
|
||||
|
||||
private void resetData() {
|
||||
synchronized (dataLock) {
|
||||
iobTable = new LongSparseArray<>();
|
||||
autosensDataTable = new LongSparseArray<>();
|
||||
basalDataTable = new LongSparseArray<>();
|
||||
absIobTable = new LongSparseArray<>();
|
||||
}
|
||||
}
|
||||
|
||||
public void createBucketedData() {
|
||||
boolean fiveMinData = isAbout5minData();
|
||||
if (lastUsed5minCalculation != null && lastUsed5minCalculation != fiveMinData) {
|
||||
// changing mode => clear cache
|
||||
getAapsLogger().debug("Invalidating cached data because of changed mode.");
|
||||
resetData();
|
||||
}
|
||||
lastUsed5minCalculation = fiveMinData;
|
||||
if (isAbout5minData())
|
||||
createBucketedData5min();
|
||||
else
|
||||
createBucketedDataRecalculated();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public GlucoseValue findNewer(long time) {
|
||||
GlucoseValue lastFound = bgReadings.get(0);
|
||||
if (lastFound.getTimestamp() < time) return null;
|
||||
for (int i = 1; i < bgReadings.size(); ++i) {
|
||||
if (bgReadings.get(i).getTimestamp() == time) return bgReadings.get(i);
|
||||
if (bgReadings.get(i).getTimestamp() > time) continue;
|
||||
lastFound = bgReadings.get(i - 1);
|
||||
if (bgReadings.get(i).getTimestamp() < time) break;
|
||||
}
|
||||
return lastFound;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public GlucoseValue findOlder(long time) {
|
||||
GlucoseValue lastFound = bgReadings.get(bgReadings.size() - 1);
|
||||
if (lastFound.getTimestamp() > time) return null;
|
||||
for (int i = bgReadings.size() - 2; i >= 0; --i) {
|
||||
if (bgReadings.get(i).getTimestamp() == time) return bgReadings.get(i);
|
||||
if (bgReadings.get(i).getTimestamp() < time) continue;
|
||||
lastFound = bgReadings.get(i + 1);
|
||||
if (bgReadings.get(i).getTimestamp() > time) break;
|
||||
}
|
||||
return lastFound;
|
||||
}
|
||||
|
||||
private void createBucketedDataRecalculated() {
|
||||
if (bgReadings == null || bgReadings.size() < 3) {
|
||||
bucketed_data = null;
|
||||
return;
|
||||
}
|
||||
|
||||
bucketed_data = new ArrayList<>();
|
||||
long currentTime = bgReadings.get(0).getTimestamp() - bgReadings.get(0).getTimestamp() % T.mins(5).msecs();
|
||||
currentTime = adjustToReferenceTime(currentTime);
|
||||
getAapsLogger().debug("Adjusted time " + dateUtil.dateAndTimeAndSecondsString(currentTime));
|
||||
//log.debug("First reading: " + new Date(currentTime).toLocaleString());
|
||||
|
||||
while (true) {
|
||||
// test if current value is older than current time
|
||||
GlucoseValue newer = findNewer(currentTime);
|
||||
GlucoseValue older = findOlder(currentTime);
|
||||
if (newer == null || older == null)
|
||||
break;
|
||||
|
||||
if (older.getTimestamp() == newer.getTimestamp()) { // direct hit
|
||||
bucketed_data.add(new InMemoryGlucoseValue(newer));
|
||||
} else {
|
||||
double bgDelta = newer.getValue() - older.getValue();
|
||||
long timeDiffToNew = newer.getTimestamp() - currentTime;
|
||||
|
||||
double currentBg = newer.getValue() - (double) timeDiffToNew / (newer.getTimestamp() - older.getTimestamp()) * bgDelta;
|
||||
InMemoryGlucoseValue newBgreading = new InMemoryGlucoseValue(currentTime, Math.round(currentBg), true);
|
||||
bucketed_data.add(newBgreading);
|
||||
//log.debug("BG: " + newBgreading.value + " (" + new Date(newBgreading.date).toLocaleString() + ") Prev: " + older.value + " (" + new Date(older.date).toLocaleString() + ") Newer: " + newer.value + " (" + new Date(newer.date).toLocaleString() + ")");
|
||||
}
|
||||
currentTime -= T.mins(5).msecs();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void createBucketedData5min() {
|
||||
if (bgReadings == null || bgReadings.size() < 3) {
|
||||
bucketed_data = null;
|
||||
return;
|
||||
}
|
||||
|
||||
bucketed_data = new ArrayList<>();
|
||||
bucketed_data.add(new InMemoryGlucoseValue(bgReadings.get(0)));
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "Adding. bgTime: " + DateUtil.toISOString(bgReadings.get(0).getTimestamp()) + " lastbgTime: " + "none-first-value" + " " + bgReadings.get(0).toString());
|
||||
int j = 0;
|
||||
for (int i = 1; i < bgReadings.size(); ++i) {
|
||||
long bgTime = bgReadings.get(i).getTimestamp();
|
||||
long lastbgTime = bgReadings.get(i - 1).getTimestamp();
|
||||
//log.error("Processing " + i + ": " + new Date(bgTime).toString() + " " + bgReadings.get(i).value + " Previous: " + new Date(lastbgTime).toString() + " " + bgReadings.get(i - 1).value);
|
||||
if (bgReadings.get(i).getValue() < 39 || bgReadings.get(i - 1).getValue() < 39) {
|
||||
throw new IllegalStateException("<39");
|
||||
}
|
||||
|
||||
long elapsed_minutes = (bgTime - lastbgTime) / (60 * 1000);
|
||||
if (Math.abs(elapsed_minutes) > 8) {
|
||||
// interpolate missing data points
|
||||
double lastbg = bgReadings.get(i - 1).getValue();
|
||||
elapsed_minutes = Math.abs(elapsed_minutes);
|
||||
//console.error(elapsed_minutes);
|
||||
long nextbgTime;
|
||||
while (elapsed_minutes > 5) {
|
||||
nextbgTime = lastbgTime - 5 * 60 * 1000;
|
||||
j++;
|
||||
double gapDelta = bgReadings.get(i).getValue() - lastbg;
|
||||
//console.error(gapDelta, lastbg, elapsed_minutes);
|
||||
double nextbg = lastbg + (5d / elapsed_minutes * gapDelta);
|
||||
InMemoryGlucoseValue newBgreading = new InMemoryGlucoseValue(nextbgTime, Math.round(nextbg), true);
|
||||
//console.error("Interpolated", bucketed_data[j]);
|
||||
bucketed_data.add(newBgreading);
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "Adding. bgTime: " + DateUtil.toISOString(bgTime) + " lastbgTime: " + DateUtil.toISOString(lastbgTime) + " " + newBgreading.toString());
|
||||
|
||||
elapsed_minutes = elapsed_minutes - 5;
|
||||
lastbg = nextbg;
|
||||
lastbgTime = nextbgTime;
|
||||
}
|
||||
j++;
|
||||
InMemoryGlucoseValue newBgreading = new InMemoryGlucoseValue(bgTime, bgReadings.get(i).getValue());
|
||||
bucketed_data.add(newBgreading);
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "Adding. bgTime: " + DateUtil.toISOString(bgTime) + " lastbgTime: " + DateUtil.toISOString(lastbgTime) + " " + newBgreading.toString());
|
||||
} else if (Math.abs(elapsed_minutes) > 2) {
|
||||
j++;
|
||||
InMemoryGlucoseValue newBgreading = new InMemoryGlucoseValue(bgTime, bgReadings.get(i).getValue());
|
||||
bucketed_data.add(newBgreading);
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "Adding. bgTime: " + DateUtil.toISOString(bgTime) + " lastbgTime: " + DateUtil.toISOString(lastbgTime) + " " + newBgreading.toString());
|
||||
} else {
|
||||
bucketed_data.get(j).setValue((bucketed_data.get(j).getValue() + bgReadings.get(i).getValue()) / 2);
|
||||
//log.error("***** Average");
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize bucketed data
|
||||
InMemoryGlucoseValue oldest = bucketed_data.get(bucketed_data.size() - 1);
|
||||
oldest.setTimestamp(adjustToReferenceTime(oldest.getTimestamp()));
|
||||
getAapsLogger().debug("Adjusted time " + dateUtil.dateAndTimeAndSecondsString(oldest.getTimestamp()));
|
||||
for (int i = bucketed_data.size() - 2; i >= 0; i--) {
|
||||
InMemoryGlucoseValue current = bucketed_data.get(i);
|
||||
InMemoryGlucoseValue previous = bucketed_data.get(i + 1);
|
||||
long msecDiff = current.getTimestamp() - previous.getTimestamp();
|
||||
long adjusted = (msecDiff - T.mins(5).msecs()) / 1000;
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "Adjusting bucketed data time. Current: " + dateUtil.dateAndTimeAndSecondsString(current.getTimestamp()) + " to: " + dateUtil.dateAndTimeAndSecondsString(previous.getTimestamp() + T.mins(5).msecs()) + " by " + adjusted + " sec");
|
||||
if (Math.abs(adjusted) > 90) {
|
||||
// too big adjustment, fallback to non 5 min data
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "Fallback to non 5 min data");
|
||||
createBucketedDataRecalculated();
|
||||
return;
|
||||
}
|
||||
current.setTimestamp(previous.getTimestamp() + T.mins(5).msecs());
|
||||
}
|
||||
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "Bucketed data created. Size: " + bucketed_data.size());
|
||||
}
|
||||
|
||||
long calculateDetectionStart(long from, boolean limitDataToOldestAvailable) {
|
||||
Profile profile = profileFunction.getProfile(from);
|
||||
double dia = Constants.defaultDIA;
|
||||
if (profile != null) dia = profile.getDia();
|
||||
|
||||
long oldestDataAvailable = treatmentsPlugin.oldestDataAvailable();
|
||||
long getBGDataFrom;
|
||||
if (limitDataToOldestAvailable) {
|
||||
getBGDataFrom = Math.max(oldestDataAvailable, (long) (from - T.hours(1).msecs() * (24 + dia)));
|
||||
if (getBGDataFrom == oldestDataAvailable)
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "Limiting data to oldest available temps: " + dateUtil.dateAndTimeAndSecondsString(oldestDataAvailable));
|
||||
} else
|
||||
getBGDataFrom = (long) (from - T.hours(1).msecs() * (24 + dia));
|
||||
return getBGDataFrom;
|
||||
}
|
||||
|
||||
public IobTotal calculateFromTreatmentsAndTempsSynchronized(long time, Profile profile) {
|
||||
synchronized (dataLock) {
|
||||
return calculateFromTreatmentsAndTemps(time, profile);
|
||||
}
|
||||
}
|
||||
|
||||
private IobTotal calculateFromTreatmentsAndTempsSynchronized(long time, AutosensResult lastAutosensResult, boolean exercise_mode, int half_basal_exercise_target, boolean isTempTarget) {
|
||||
synchronized (dataLock) {
|
||||
return calculateFromTreatmentsAndTemps(time, lastAutosensResult, exercise_mode, half_basal_exercise_target, isTempTarget);
|
||||
}
|
||||
}
|
||||
|
||||
IobTotal calculateFromTreatmentsAndTemps(long time, Profile profile) {
|
||||
long now = System.currentTimeMillis();
|
||||
time = roundUpTime(time);
|
||||
if (time < now && iobTable.get(time) != null) {
|
||||
//og.debug(">>> calculateFromTreatmentsAndTemps Cache hit " + new Date(time).toLocaleString());
|
||||
return iobTable.get(time);
|
||||
} else {
|
||||
//log.debug(">>> calculateFromTreatmentsAndTemps Cache miss " + new Date(time).toLocaleString());
|
||||
}
|
||||
IobTotal bolusIob = treatmentsPlugin.getCalculationToTimeTreatments(time).round();
|
||||
IobTotal basalIob = treatmentsPlugin.getCalculationToTimeTempBasals(time, true, now).round();
|
||||
// OpenAPSSMB only
|
||||
// Add expected zero temp basal for next 240 mins
|
||||
IobTotal basalIobWithZeroTemp = basalIob.copy();
|
||||
TemporaryBasal t = new TemporaryBasal(injector)
|
||||
.date(now + 60 * 1000L)
|
||||
.duration(240)
|
||||
.absolute(0);
|
||||
if (t.date < time) {
|
||||
IobTotal calc = t.iobCalc(time, profile);
|
||||
basalIobWithZeroTemp.plus(calc);
|
||||
}
|
||||
|
||||
basalIob.iobWithZeroTemp = IobTotal.combine(bolusIob, basalIobWithZeroTemp).round();
|
||||
|
||||
IobTotal iobTotal = IobTotal.combine(bolusIob, basalIob).round();
|
||||
if (time < System.currentTimeMillis()) {
|
||||
iobTable.put(time, iobTotal);
|
||||
}
|
||||
return iobTotal;
|
||||
}
|
||||
|
||||
public IobTotal calculateAbsInsulinFromTreatmentsAndTempsSynchronized(long time, Profile profile) {
|
||||
synchronized (dataLock) {
|
||||
long now = System.currentTimeMillis();
|
||||
time = roundUpTime(time);
|
||||
if (time < now && absIobTable.get(time) != null) {
|
||||
//og.debug(">>> calculateFromTreatmentsAndTemps Cache hit " + new Date(time).toLocaleString());
|
||||
return absIobTable.get(time);
|
||||
} else {
|
||||
//log.debug(">>> calculateFromTreatmentsAndTemps Cache miss " + new Date(time).toLocaleString());
|
||||
}
|
||||
IobTotal bolusIob = treatmentsPlugin.getCalculationToTimeTreatments(time).round();
|
||||
IobTotal basalIob = treatmentsPlugin.getAbsoluteIOBTempBasals(time).round();
|
||||
|
||||
IobTotal iobTotal = IobTotal.combine(bolusIob, basalIob).round();
|
||||
if (time < System.currentTimeMillis()) {
|
||||
absIobTable.put(time, iobTotal);
|
||||
}
|
||||
return iobTotal;
|
||||
}
|
||||
}
|
||||
|
||||
private IobTotal calculateFromTreatmentsAndTemps(long time, AutosensResult lastAutosensResult, boolean exercise_mode, int half_basal_exercise_target, boolean isTempTarget) {
|
||||
long now = DateUtil.now();
|
||||
|
||||
IobTotal bolusIob = treatmentsPlugin.getCalculationToTimeTreatments(time).round();
|
||||
IobTotal basalIob = treatmentsPlugin.getCalculationToTimeTempBasals(time, now, lastAutosensResult, exercise_mode, half_basal_exercise_target, isTempTarget).round();
|
||||
// OpenAPSSMB only
|
||||
// Add expected zero temp basal for next 240 mins
|
||||
IobTotal basalIobWithZeroTemp = basalIob.copy();
|
||||
TemporaryBasal t = new TemporaryBasal(injector)
|
||||
.date(now + 60 * 1000L)
|
||||
.duration(240)
|
||||
.absolute(0);
|
||||
if (t.date < time) {
|
||||
Profile profile = profileFunction.getProfile(t.date);
|
||||
if (profile != null) {
|
||||
IobTotal calc = t.iobCalc(time, profile, lastAutosensResult, exercise_mode, half_basal_exercise_target, isTempTarget);
|
||||
basalIobWithZeroTemp.plus(calc);
|
||||
}
|
||||
}
|
||||
|
||||
basalIob.iobWithZeroTemp = IobTotal.combine(bolusIob, basalIobWithZeroTemp).round();
|
||||
|
||||
return IobTotal.combine(bolusIob, basalIob).round();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long findPreviousTimeFromBucketedData(long time) {
|
||||
if (bucketed_data == null)
|
||||
return null;
|
||||
for (int index = 0; index < bucketed_data.size(); index++) {
|
||||
if (bucketed_data.get(index).getTimestamp() <= time)
|
||||
return bucketed_data.get(index).getTimestamp();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public BasalData getBasalData(Profile profile, long time) {
|
||||
synchronized (dataLock) {
|
||||
long now = System.currentTimeMillis();
|
||||
time = roundUpTime(time);
|
||||
BasalData retval = basalDataTable.get(time);
|
||||
if (retval == null) {
|
||||
retval = new BasalData();
|
||||
TemporaryBasal tb = treatmentsPlugin.getTempBasalFromHistory(time);
|
||||
retval.basal = profile.getBasal(time);
|
||||
if (tb != null) {
|
||||
retval.isTempBasalRunning = true;
|
||||
retval.tempBasalAbsolute = tb.tempBasalConvertedToAbsolute(time, profile);
|
||||
} else {
|
||||
retval.isTempBasalRunning = false;
|
||||
retval.tempBasalAbsolute = retval.basal;
|
||||
}
|
||||
if (time < now) {
|
||||
basalDataTable.append(time, retval);
|
||||
}
|
||||
//log.debug(">>> getBasalData Cache miss " + new Date(time).toLocaleString());
|
||||
} else {
|
||||
//log.debug(">>> getBasalData Cache hit " + new Date(time).toLocaleString());
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AutosensData getAutosensData(long time) {
|
||||
synchronized (dataLock) {
|
||||
long now = System.currentTimeMillis();
|
||||
if (time > now) {
|
||||
return null;
|
||||
}
|
||||
Long previous = findPreviousTimeFromBucketedData(time);
|
||||
if (previous == null) {
|
||||
return null;
|
||||
}
|
||||
time = roundUpTime(previous);
|
||||
AutosensData data = autosensDataTable.get(time);
|
||||
if (data != null) {
|
||||
//log.debug(">>> AUTOSENSDATA Cache hit " + data.toString());
|
||||
return data;
|
||||
} else {
|
||||
//log.debug(">>> AUTOSENSDATA Cache miss " + new Date(time).toLocaleString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AutosensData getLastAutosensDataSynchronized(String reason) {
|
||||
if (thread != null && thread.isAlive()) {
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "AUTOSENSDATA is waiting for calculation thread: " + reason);
|
||||
try {
|
||||
thread.join(5000);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "AUTOSENSDATA finished waiting for calculation thread: " + reason);
|
||||
}
|
||||
synchronized (dataLock) {
|
||||
return getLastAutosensData(reason);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
public CobInfo getCobInfo(boolean _synchronized, String reason) {
|
||||
AutosensData autosensData = _synchronized ? getLastAutosensDataSynchronized(reason) : getLastAutosensData(reason);
|
||||
Double displayCob = null;
|
||||
double futureCarbs = 0;
|
||||
long now = now();
|
||||
List<Treatment> treatments = treatmentsPlugin.getTreatmentsFromHistory();
|
||||
|
||||
if (autosensData != null) {
|
||||
displayCob = autosensData.cob;
|
||||
for (Treatment treatment : treatments) {
|
||||
if (!treatment.isValid) continue;
|
||||
if (IobCobCalculatorPlugin.roundUpTime(treatment.date) > IobCobCalculatorPlugin.roundUpTime(autosensData.time)
|
||||
&& treatment.date <= now && treatment.carbs > 0) {
|
||||
displayCob += treatment.carbs;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Treatment treatment : treatments) {
|
||||
if (!treatment.isValid) continue;
|
||||
if (treatment.date > now && treatment.carbs > 0) {
|
||||
futureCarbs += treatment.carbs;
|
||||
}
|
||||
}
|
||||
return new CobInfo(displayCob, futureCarbs);
|
||||
}
|
||||
|
||||
public double slowAbsorptionPercentage(int timeInMinutes) {
|
||||
double sum = 0;
|
||||
int count = 0;
|
||||
int valuesToProcess = timeInMinutes / 5;
|
||||
synchronized (dataLock) {
|
||||
for (int i = autosensDataTable.size() - 1; i >= 0 && count < valuesToProcess; i--) {
|
||||
if (autosensDataTable.valueAt(i).failoverToMinAbsorbtionRate)
|
||||
sum++;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return sum / count;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AutosensData getLastAutosensData(String reason) {
|
||||
if (autosensDataTable.size() < 1) {
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "AUTOSENSDATA null: autosensDataTable empty (" + reason + ")");
|
||||
return null;
|
||||
}
|
||||
AutosensData data;
|
||||
try {
|
||||
data = autosensDataTable.valueAt(autosensDataTable.size() - 1);
|
||||
} catch (Exception e) {
|
||||
// data can be processed on the background
|
||||
// in this rare case better return null and do not block UI
|
||||
// APS plugin should use getLastAutosensDataSynchronized where the blocking is not an issue
|
||||
getAapsLogger().error("AUTOSENSDATA null: Exception catched (" + reason + ")");
|
||||
return null;
|
||||
}
|
||||
if (data == null) {
|
||||
getAapsLogger().error("AUTOSENSDATA null: data==null");
|
||||
return null;
|
||||
}
|
||||
if (data.time < System.currentTimeMillis() - 11 * 60 * 1000) {
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "AUTOSENSDATA null: data is old (" + reason + ") size()=" + autosensDataTable.size() + " lastdata=" + dateUtil.dateAndTimeAndSecondsString(data.time));
|
||||
return null;
|
||||
} else {
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "AUTOSENSDATA (" + reason + ") " + data.toString());
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String lastDataTime() {
|
||||
if (autosensDataTable.size() > 0)
|
||||
return dateUtil.dateAndTimeAndSecondsString(autosensDataTable.valueAt(autosensDataTable.size() - 1).time);
|
||||
else
|
||||
return "autosensDataTable empty";
|
||||
}
|
||||
|
||||
public MealData getMealData() {
|
||||
MealData result = new MealData();
|
||||
|
||||
Profile profile = profileFunction.getProfile();
|
||||
if (profile == null) return result;
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long dia_ago = now - (Double.valueOf(profile.getDia() * T.hours(1).msecs())).longValue();
|
||||
|
||||
double maxAbsorptionHours = Constants.DEFAULT_MAX_ABSORPTION_TIME;
|
||||
if (sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled()) {
|
||||
maxAbsorptionHours = sp.getDouble(R.string.key_absorption_maxtime, Constants.DEFAULT_MAX_ABSORPTION_TIME);
|
||||
} else {
|
||||
maxAbsorptionHours = sp.getDouble(R.string.key_absorption_cutoff, Constants.DEFAULT_MAX_ABSORPTION_TIME);
|
||||
}
|
||||
long absorptionTime_ago = now - (Double.valueOf(maxAbsorptionHours * T.hours(1).msecs())).longValue();
|
||||
|
||||
List<Treatment> treatments = treatmentsPlugin.getTreatmentsFromHistory();
|
||||
for (Treatment treatment : treatments) {
|
||||
if (!treatment.isValid)
|
||||
continue;
|
||||
long t = treatment.date;
|
||||
|
||||
if (t > dia_ago && t <= now) {
|
||||
if (treatment.insulin > 0 && treatment.mealBolus) {
|
||||
result.boluses += treatment.insulin;
|
||||
}
|
||||
}
|
||||
if (t > absorptionTime_ago && t <= now) {
|
||||
if (treatment.carbs >= 1) {
|
||||
result.carbs += treatment.carbs;
|
||||
if (t > result.lastCarbTime)
|
||||
result.lastCarbTime = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AutosensData autosensData = getLastAutosensDataSynchronized("getMealData()");
|
||||
if (autosensData != null) {
|
||||
result.mealCOB = autosensData.cob;
|
||||
result.slopeFromMinDeviation = autosensData.slopeFromMinDeviation;
|
||||
result.slopeFromMaxDeviation = autosensData.slopeFromMaxDeviation;
|
||||
result.usedMinCarbsImpact = autosensData.usedMinCarbsImpact;
|
||||
}
|
||||
result.lastBolusTime = treatmentsPlugin.getLastBolusTime();
|
||||
return result;
|
||||
}
|
||||
|
||||
public IobTotal[] calculateIobArrayInDia(Profile profile) {
|
||||
// predict IOB out to DIA plus 30m
|
||||
long time = System.currentTimeMillis();
|
||||
time = roundUpTime(time);
|
||||
int len = (int) ((profile.getDia() * 60 + 30) / 5);
|
||||
IobTotal[] array = new IobTotal[len];
|
||||
int pos = 0;
|
||||
for (int i = 0; i < len; i++) {
|
||||
long t = time + i * 5 * 60000;
|
||||
IobTotal iob = calculateFromTreatmentsAndTempsSynchronized(t, profile);
|
||||
array[pos] = iob;
|
||||
pos++;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
public IobTotal[] calculateIobArrayForSMB(AutosensResult lastAutosensResult, boolean exercise_mode, int half_basal_exercise_target, boolean isTempTarget) {
|
||||
// predict IOB out to DIA plus 30m
|
||||
long now = DateUtil.now();
|
||||
int len = (4 * 60) / 5;
|
||||
IobTotal[] array = new IobTotal[len];
|
||||
int pos = 0;
|
||||
for (int i = 0; i < len; i++) {
|
||||
long t = now + i * 5 * 60000;
|
||||
IobTotal iob = calculateFromTreatmentsAndTempsSynchronized(t, lastAutosensResult, exercise_mode, half_basal_exercise_target, isTempTarget);
|
||||
array[pos] = iob;
|
||||
pos++;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
public String iobArrayToString(IobTotal[] array) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("[");
|
||||
for (IobTotal i : array) {
|
||||
sb.append(DecimalFormatter.to2Decimal(i.iob));
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
AutosensResult detectSensitivityWithLock(long fromTime, long toTime) {
|
||||
synchronized (dataLock) {
|
||||
return activePlugin.getActiveSensitivity().detectSensitivity(this, fromTime, toTime);
|
||||
}
|
||||
}
|
||||
|
||||
public static JSONArray convertToJSONArray(IobTotal[] iobArray) {
|
||||
JSONArray array = new JSONArray();
|
||||
for (int i = 0; i < iobArray.length; i++) {
|
||||
array.put(iobArray[i].determineBasalJson());
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
public void stopCalculation(String from) {
|
||||
if (thread != null && thread.getState() != Thread.State.TERMINATED) {
|
||||
stopCalculationTrigger = true;
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "Stopping calculation thread: " + from);
|
||||
while (thread.getState() != Thread.State.TERMINATED) {
|
||||
SystemClock.sleep(100);
|
||||
}
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "Calculation thread stopped: " + from);
|
||||
}
|
||||
}
|
||||
|
||||
public void runCalculation(String from, long end, boolean bgDataReload, boolean limitDataToOldestAvailable, Event cause) {
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "Starting calculation thread: " + from + " to " + dateUtil.dateAndTimeAndSecondsString(end));
|
||||
if (thread == null || thread.getState() == Thread.State.TERMINATED) {
|
||||
if (sensitivityOref1Plugin.isEnabled())
|
||||
thread = new IobCobOref1Thread(injector, this, treatmentsPlugin, from, end, bgDataReload, limitDataToOldestAvailable, cause);
|
||||
else
|
||||
thread = new IobCobThread(injector, this, treatmentsPlugin, from, end, bgDataReload, limitDataToOldestAvailable, cause);
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
|
||||
// When historical data is changed (comming from NS etc) finished calculations after this date must be invalidated
|
||||
private void newHistoryData(EventNewHistoryData ev, boolean bgDataReload) {
|
||||
//log.debug("Locking onNewHistoryData");
|
||||
stopCalculation("onEventNewHistoryData");
|
||||
synchronized (dataLock) {
|
||||
// clear up 5 min back for proper COB calculation
|
||||
long time = ev.getTime() - 5 * 60 * 1000L;
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "Invalidating cached data to: " + dateUtil.dateAndTimeAndSecondsString(time));
|
||||
for (int index = iobTable.size() - 1; index >= 0; index--) {
|
||||
if (iobTable.keyAt(index) > time) {
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "Removing from iobTable: " + dateUtil.dateAndTimeAndSecondsString(iobTable.keyAt(index)));
|
||||
iobTable.removeAt(index);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int index = absIobTable.size() - 1; index >= 0; index--) {
|
||||
if (absIobTable.keyAt(index) > time) {
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "Removing from absIobTable: " + dateUtil.dateAndTimeAndSecondsString(absIobTable.keyAt(index)));
|
||||
absIobTable.removeAt(index);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int index = autosensDataTable.size() - 1; index >= 0; index--) {
|
||||
if (autosensDataTable.keyAt(index) > time) {
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "Removing from autosensDataTable: " + dateUtil.dateAndTimeAndSecondsString(autosensDataTable.keyAt(index)));
|
||||
autosensDataTable.removeAt(index);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int index = basalDataTable.size() - 1; index >= 0; index--) {
|
||||
if (basalDataTable.keyAt(index) > time) {
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "Removing from basalDataTable: " + dateUtil.dateAndTimeAndSecondsString(basalDataTable.keyAt(index)));
|
||||
basalDataTable.removeAt(index);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
runCalculation("onEventNewHistoryData", System.currentTimeMillis(), bgDataReload, true, ev);
|
||||
//log.debug("Releasing onNewHistoryData");
|
||||
}
|
||||
|
||||
public void clearCache() {
|
||||
synchronized (dataLock) {
|
||||
getAapsLogger().debug(LTag.AUTOSENS, "Clearing cached data.");
|
||||
iobTable = new LongSparseArray<>();
|
||||
autosensDataTable = new LongSparseArray<>();
|
||||
basalDataTable = new LongSparseArray<>();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Return last BgReading from database or null if db is empty
|
||||
*/
|
||||
@Nullable
|
||||
public GlucoseValue lastBg() {
|
||||
List<GlucoseValue> bgList = getBgReadings();
|
||||
|
||||
if (bgList == null)
|
||||
return null;
|
||||
|
||||
for (int i = 0; i < bgList.size(); i++)
|
||||
if (bgList.get(i).getValue() >= 39)
|
||||
return bgList.get(i);
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return bg reading if not old ( <9 min )
|
||||
* or null if older
|
||||
*/
|
||||
@Nullable
|
||||
public GlucoseValue actualBg() {
|
||||
GlucoseValue lastBg = lastBg();
|
||||
|
||||
if (lastBg == null)
|
||||
return null;
|
||||
|
||||
if (lastBg.getTimestamp() > System.currentTimeMillis() - 9 * 60 * 1000)
|
||||
return lastBg;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// From https://gist.github.com/IceCreamYou/6ffa1b18c4c8f6aeaad2
|
||||
// Returns the value at a given percentile in a sorted numeric array.
|
||||
// "Linear interpolation between closest ranks" method
|
||||
public static double percentile(Double[] arr, double p) {
|
||||
if (arr.length == 0) return 0;
|
||||
if (p <= 0) return arr[0];
|
||||
if (p >= 1) return arr[arr.length - 1];
|
||||
|
||||
double index = arr.length * p,
|
||||
lower = Math.floor(index),
|
||||
upper = lower + 1,
|
||||
weight = index % 1;
|
||||
|
||||
if (upper >= arr.length) return arr[(int) lower];
|
||||
return arr[(int) lower] * (1 - weight) + arr[(int) upper] * weight;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,804 @@
|
|||
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.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.collections.ArrayList
|
||||
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: EventConfigBuilderChange? ->
|
||||
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: EventNewBasalProfile? ->
|
||||
if (event == null) { // on init no need of reset
|
||||
return@subscribe
|
||||
}
|
||||
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: EventNewBG? ->
|
||||
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: EventPreferenceChange ->
|
||||
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: EventAppInitialized? -> 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: EventNewHistoryData -> newHistoryData(event, false) }, fabricPrivacy::logException)
|
||||
)
|
||||
// EventNewHistoryBgData
|
||||
disposable.add(rxBus
|
||||
.toObservable(EventNewHistoryBgData::class.java)
|
||||
.observeOn(aapsSchedulers.io)
|
||||
.subscribe({ event: EventNewHistoryBgData -> 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 = arrayOf<IobTotal>()
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -125,7 +125,7 @@ public class IobCobOref1Thread extends Thread {
|
|||
return;
|
||||
}
|
||||
|
||||
long prevDataTime = IobCobCalculatorPlugin.roundUpTime(bucketed_data.get(bucketed_data.size() - 3).getTimestamp());
|
||||
long prevDataTime = IobCobCalculatorPlugin.Companion.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
|
||||
|
@ -133,15 +133,15 @@ public class IobCobOref1Thread extends Thread {
|
|||
String progress = i + (buildHelper.isDev() ? " (" + from + ")" : "");
|
||||
rxBus.send(new EventIobCalculationProgress(progress));
|
||||
|
||||
if (iobCobCalculatorPlugin.stopCalculationTrigger) {
|
||||
iobCobCalculatorPlugin.stopCalculationTrigger = false;
|
||||
if (iobCobCalculatorPlugin.getStopCalculationTrigger()) {
|
||||
iobCobCalculatorPlugin.setStopCalculationTrigger(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()))
|
||||
bgTime = IobCobCalculatorPlugin.Companion.roundUpTime(bgTime);
|
||||
if (bgTime > IobCobCalculatorPlugin.Companion.roundUpTime(now()))
|
||||
continue;
|
||||
|
||||
AutosensData existing;
|
||||
|
|
|
@ -122,7 +122,7 @@ public class IobCobThread extends Thread {
|
|||
return;
|
||||
}
|
||||
|
||||
long prevDataTime = IobCobCalculatorPlugin.roundUpTime(bucketed_data.get(bucketed_data.size() - 3).getTimestamp());
|
||||
long prevDataTime = IobCobCalculatorPlugin.Companion.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
|
||||
|
@ -130,15 +130,15 @@ public class IobCobThread extends Thread {
|
|||
String progress = i + (buildHelper.isDev() ? " (" + from + ")" : "");
|
||||
rxBus.send(new EventIobCalculationProgress(progress));
|
||||
|
||||
if (iobCobCalculatorPlugin.stopCalculationTrigger) {
|
||||
iobCobCalculatorPlugin.stopCalculationTrigger = false;
|
||||
if (iobCobCalculatorPlugin.getStopCalculationTrigger()) {
|
||||
iobCobCalculatorPlugin.setStopCalculationTrigger(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()))
|
||||
bgTime = IobCobCalculatorPlugin.Companion.roundUpTime(bgTime);
|
||||
if (bgTime > IobCobCalculatorPlugin.Companion.roundUpTime(now()))
|
||||
continue;
|
||||
|
||||
AutosensData existing;
|
||||
|
@ -190,8 +190,8 @@ public class IobCobThread extends Thread {
|
|||
|
||||
// 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);
|
||||
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());
|
||||
|
@ -219,9 +219,6 @@ public class IobCobThread extends Thread {
|
|||
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);
|
||||
|
@ -301,7 +298,6 @@ public class IobCobThread extends Thread {
|
|||
} 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())
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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.logDirectory
|
||||
val extFilesDir = loggerUtils.logDirectory
|
||||
val engineeringModeSemaphore = File(extFilesDir, "engineering__mode")
|
||||
|
||||
engineeringMode = engineeringModeSemaphore.exists() && engineeringModeSemaphore.isFile
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue