IobCobCalculator refactor

This commit is contained in:
Milos Kozak 2021-04-13 18:33:44 +02:00
parent c637b7072c
commit 36040b7ed2
38 changed files with 1030 additions and 1022 deletions

View file

@ -2,6 +2,22 @@
<code_scheme name="Project" version="173">
<option name="AUTODETECT_INDENTS" value="false" />
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="java.util" alias="false" withSubpackages="false" />
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
<package name="io.ktor" alias="false" withSubpackages="true" />
</value>
</option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="java" alias="false" withSubpackages="true" />
<package name="javax" alias="false" withSubpackages="true" />
<package name="kotlin" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
<option name="ALIGN_IN_COLUMNS_CASE_BRANCH" value="true" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="6" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="6" />

View file

@ -144,7 +144,7 @@ class CarbsDialog : DialogFragmentWithDate() {
validateInputs()
}
iobCobCalculator.actualBg()?.let { bgReading ->
iobCobCalculator.ads.actualBg()?.let { bgReading ->
if (bgReading.value < 72)
binding.hypoTt.isChecked = true
}

View file

@ -267,7 +267,7 @@ class WizardDialog : DaggerDialogFragment() {
binding.bgInput.setStep(0.1)
// Set BG if not old
binding.bgInput.value = iobCobCalculator.actualBg()?.valueToUnits(units) ?: 0.0
binding.bgInput.value = iobCobCalculator.ads.actualBg()?.valueToUnits(units) ?: 0.0
binding.ttcheckbox.isEnabled = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() is ValueWrapper.Existing
// IOB calculation

View file

@ -124,7 +124,7 @@ open class LoopPlugin @Inject constructor(
.subscribe({ event: EventAutosensCalculationFinished ->
// Autosens calculation not triggered by a new BG
if (event.cause !is EventNewBG) return@subscribe
val glucoseValue = iobCobCalculator.actualBg() ?: return@subscribe
val glucoseValue = iobCobCalculator.ads.actualBg() ?: return@subscribe
// BG outdated
// already looped with that value
if (glucoseValue.timestamp <= lastBgTriggeredRun) return@subscribe

View file

@ -48,7 +48,7 @@ class Objective0(injector: HasAndroidInjector) : Objective(injector, "config", R
})
tasks.add(object : Task(this, R.string.hasbgdata) {
override fun isCompleted(): Boolean {
return iobCobCalculator.lastBg() != null
return iobCobCalculator.ads.lastBg() != null
}
})
tasks.add(object : Task(this, R.string.loopenabled) {

View file

@ -119,7 +119,7 @@ class DataBroadcastPlugin @Inject constructor(
}
private fun bgStatus(bundle: Bundle) {
val lastBG = iobCobCalculator.lastBg() ?: return
val lastBG = iobCobCalculator.ads.lastBg() ?: return
val glucoseStatus = glucoseStatusProvider.glucoseStatusData ?: return
bundle.putDouble("glucoseMgdl", lastBG.value) // last BG in mgdl

View file

@ -411,7 +411,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
}
private fun onClickQuickWizard() {
val actualBg = iobCobCalculator.actualBg()
val actualBg = iobCobCalculator.ads.actualBg()
val profile = profileFunction.getProfile()
val profileName = profileFunction.getProfileName()
val pump = activePlugin.activePump
@ -446,11 +446,11 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
@SuppressLint("SetTextI18n")
private fun processButtonsVisibility() {
val lastBG = iobCobCalculator.lastBg()
val lastBG = iobCobCalculator.ads.lastBg()
val pump = activePlugin.activePump
val profile = profileFunction.getProfile()
val profileName = profileFunction.getProfileName()
val actualBG = iobCobCalculator.actualBg()
val actualBG = iobCobCalculator.ads.actualBg()
// QuickWizard button
val quickWizardEntry = quickWizard.getActive()
@ -562,8 +562,8 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
binding.loopPumpStatusLayout.loopLayout.visibility = View.VISIBLE
val profile = profileFunction.getProfile() ?: return
val actualBG = iobCobCalculator.actualBg()
val lastBG = iobCobCalculator.lastBg()
val actualBG = iobCobCalculator.ads.actualBg()
val lastBG = iobCobCalculator.ads.lastBg()
val pump = activePlugin.activePump
val units = profileFunction.getUnits()
val lowLine = defaultValueHelper.determineLowLine()
@ -812,7 +812,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
}
binding.infoLayout.sensitivity.text =
iobCobCalculator.getLastAutosensData("Overview")?.let { autosensData ->
iobCobCalculator.ads.getLastAutosensData("Overview", aapsLogger, dateUtil)?.let { autosensData ->
String.format(Locale.ENGLISH, "%.0f%%", autosensData.autosensResult.ratio * 100)
} ?: ""
}

View file

@ -62,7 +62,7 @@ class GraphData(
}
fun addBucketedData(fromTime: Long, toTime: Long) {
val bucketedData = iobCobCalculator.getBucketedDataTableCopy() ?: return
val bucketedData = iobCobCalculator.ads.getBucketedDataTableCopy() ?: return
if (bucketedData.isEmpty()) {
aapsLogger.debug("No bucketed data.")
return
@ -77,7 +77,7 @@ class GraphData(
fun addBgReadings(fromTime: Long, toTime: Long, highLine: Double, predictions: MutableList<GlucoseValueDataPoint>?) {
var maxBgValue = Double.MIN_VALUE
bgReadingsArray = repository.compatGetBgReadingsDataFromTime(fromTime, toTime, true).blockingGet()
bgReadingsArray = repository.compatGetBgReadingsDataFromTime(fromTime, toTime, false).blockingGet()
if (bgReadingsArray?.isEmpty() != false) {
aapsLogger.debug("No BG data.")
maxY = if (units == Constants.MGDL) 180.0 else 10.0
@ -302,8 +302,7 @@ class GraphData(
private fun getNearestBg(date: Long): Double {
bgReadingsArray?.let { bgReadingsArray ->
for (r in bgReadingsArray.indices) {
val reading = bgReadingsArray[r]
for (reading in bgReadingsArray) {
if (reading.timestamp > date) continue
return Profile.fromMgdlToUnits(reading.value, units)
}
@ -325,7 +324,7 @@ class GraphData(
time += 5 * 60 * 1000L
continue
}
total = iobCobCalculator.calculateFromTreatmentsAndTempsSynchronized(time, profile)
total = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile)
val act: Double = total.activity
if (time <= now) actArrayHist.add(ScaledDataPoint(time, act, actScale)) else actArrayPrediction.add(ScaledDataPoint(time, act, actScale))
maxIAValue = max(maxIAValue, abs(act))
@ -366,10 +365,10 @@ class GraphData(
time += 5 * 60 * 1000L
continue
}
val deviation = if (devBgiScale) iobCobCalculator.getAutosensData(time)?.deviation
val deviation = if (devBgiScale) iobCobCalculator.ads.getAutosensDataAtTime(time)?.deviation
?: 0.0 else 0.0
total = iobCobCalculator.calculateFromTreatmentsAndTempsSynchronized(time, profile)
total = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile)
val bgi: Double = total.activity * profile.getIsfMgdl(time) * 5.0
if (time <= now) bgiArrayHist.add(ScaledDataPoint(time, bgi, bgiScale)) else bgiArrayPrediction.add(ScaledDataPoint(time, bgi, bgiScale))
maxBGIValue = max(maxBGIValue, max(abs(bgi), deviation))
@ -408,8 +407,8 @@ class GraphData(
var iob = 0.0
var absIob = 0.0
if (profile != null) {
iob = iobCobCalculator.calculateFromTreatmentsAndTempsSynchronized(time, profile).iob
if (absScale) absIob = iobCobCalculator.calculateAbsInsulinFromTreatmentsAndTempsSynchronized(time).iob
iob = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile).iob
if (absScale) absIob = iobCobCalculator.calculateAbsInsulinFromTreatmentsAndTemps(time).iob
}
if (abs(lastIob - iob) > 0.02) {
if (abs(lastIob - iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, iobScale))
@ -465,7 +464,7 @@ class GraphData(
while (time <= toTime) {
val profile = profileFunction.getProfile(time)
var iob = 0.0
if (profile != null) iob = iobCobCalculator.calculateAbsInsulinFromTreatmentsAndTempsSynchronized(time).iob
if (profile != null) iob = iobCobCalculator.calculateAbsInsulinFromTreatmentsAndTemps(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))
@ -497,7 +496,7 @@ class GraphData(
val cobScale = Scale()
var time = fromTime
while (time <= toTime) {
iobCobCalculator.getAutosensData(time)?.let { autosensData ->
iobCobCalculator.ads.getAutosensDataAtTime(time)?.let { autosensData ->
val cob = autosensData.cob.toInt()
if (cob != lastCob) {
if (autosensData.carbsFromBolus > 0) cobArray.add(ScaledDataPoint(time, lastCob.toDouble(), cobScale))
@ -543,11 +542,11 @@ class GraphData(
// if align Dev Scale with BGI scale, then calculate BGI value, else bgi = 0.0
val bgi: Double = if (devBgiScale) {
val profile = profileFunction.getProfile(time) ?: continue
total = iobCobCalculator.calculateFromTreatmentsAndTempsSynchronized(time, profile)
total = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile)
total.activity * profile.getIsfMgdl(time) * 5.0
} else 0.0
iobCobCalculator.getAutosensData(time)?.let { autosensData ->
iobCobCalculator.ads.getAutosensDataAtTime(time)?.let { autosensData ->
var color = resourceHelper.gc(R.color.deviationblack) // "="
if (autosensData.type == "" || autosensData.type == "non-meal") {
if (autosensData.pastSensitivity == "C") color = resourceHelper.gc(R.color.deviationgrey)
@ -583,7 +582,7 @@ class GraphData(
val ratioScale = Scale()
var time = fromTime
while (time <= toTime) {
iobCobCalculator.getAutosensData(time)?.let { autosensData ->
iobCobCalculator.ads.getAutosensDataAtTime(time)?.let { autosensData ->
ratioArray.add(ScaledDataPoint(time, autosensData.autosensResult.ratio - 1, ratioScale))
maxRatioValueFound = max(maxRatioValueFound, autosensData.autosensResult.ratio - 1)
minRatioValueFound = min(minRatioValueFound, autosensData.autosensResult.ratio - 1)
@ -613,7 +612,7 @@ class GraphData(
val dsMinScale = Scale()
var time = fromTime
while (time <= toTime) {
iobCobCalculator.getAutosensData(time)?.let { autosensData ->
iobCobCalculator.ads.getAutosensDataAtTime(time)?.let { autosensData ->
dsMaxArray.add(ScaledDataPoint(time, autosensData.slopeFromMaxDeviation, dsMaxScale))
dsMinArray.add(ScaledDataPoint(time, autosensData.slopeFromMinDeviation, dsMinScale))
maxFromMaxValueFound = max(maxFromMaxValueFound, abs(autosensData.slopeFromMaxDeviation))

View file

@ -130,7 +130,7 @@ class PersistentNotificationPlugin @Inject constructor(
if (profileFunction.isProfileValid("Notification")) {
var line1aa: String
val units = profileFunction.getUnits()
val lastBG = iobCobCalculator.lastBg()
val lastBG = iobCobCalculator.ads.lastBg()
val glucoseStatus = glucoseStatusProvider.glucoseStatusData
if (lastBG != null) {
line1aa = lastBG.valueToUnitsString(units)

View file

@ -310,8 +310,8 @@ class SmsCommunicatorPlugin @Inject constructor(
}
private fun processBG(receivedSms: Sms) {
val actualBG = iobCobCalculator.actualBg()
val lastBG = iobCobCalculator.lastBg()
val actualBG = iobCobCalculator.ads.actualBg()
val lastBG = iobCobCalculator.ads.lastBg()
var reply = ""
val units = profileFunction.getUnits()
if (actualBG != null) {
@ -854,7 +854,7 @@ class SmsCommunicatorPlugin @Inject constructor(
} else sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.wrongformat)))
}
fun toTodayTime(hh_colon_mm: String): Long {
private fun toTodayTime(hh_colon_mm: String): Long {
val p = Pattern.compile("(\\d+):(\\d+)( a.m.| p.m.| AM| PM|AM|PM|)")
val m = p.matcher(hh_colon_mm)
var retval: Long = 0

View file

@ -207,7 +207,7 @@ class ActionStringHandler @Inject constructor(
sendError("No profile found!")
return
}
val bgReading = iobCobCalculator.actualBg()
val bgReading = iobCobCalculator.ads.actualBg()
if (bgReading == null) {
sendError("No recent BG to base calculation on!")
return

View file

@ -279,7 +279,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
private void sendData() {
GlucoseValue lastBG = iobCobCalculator.lastBg();
GlucoseValue lastBG = iobCobCalculator.getAds().lastBg();
// Log.d(TAG, logPrefix + "LastBg=" + lastBG);
if (lastBG != null) {
GlucoseStatus glucoseStatus = glucoseStatusProvider.getGlucoseStatusData();
@ -381,7 +381,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
googleApiConnect();
}
long startTime = System.currentTimeMillis() - (long) (60000 * 60 * 5.5);
GlucoseValue last_bg = iobCobCalculator.lastBg();
GlucoseValue last_bg = iobCobCalculator.getAds().lastBg();
if (last_bg == null) return;

View file

@ -5,7 +5,6 @@ import androidx.collection.LongSparseArray
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.InMemoryGlucoseValue
import info.nightscout.androidaps.data.IobTotal
import info.nightscout.androidaps.data.MealData
import info.nightscout.androidaps.data.Profile
@ -13,7 +12,6 @@ import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.database.entities.Bolus
import info.nightscout.androidaps.database.entities.ExtendedBolus
import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.database.entities.TemporaryBasal
import info.nightscout.androidaps.database.interfaces.end
import info.nightscout.androidaps.events.*
@ -41,11 +39,9 @@ import io.reactivex.rxkotlin.plusAssign
import org.json.JSONArray
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.abs
import kotlin.math.floor
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToLong
@Singleton
open class IobCobCalculatorPlugin @Inject constructor(
@ -76,17 +72,11 @@ open class IobCobCalculatorPlugin @Inject constructor(
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
internal var bgReadings: List<GlucoseValue> = listOf() // newest at index 0
internal var bucketedData: MutableList<InMemoryGlucoseValue>? = null
override var ads: AutosensDataStore = AutosensDataStore()
// 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
override val dataLock = Any()
private val dataLock = Any()
var stopCalculationTrigger = false
private var thread: Thread? = null
@ -96,12 +86,12 @@ open class IobCobCalculatorPlugin @Inject constructor(
disposable += rxBus
.toObservable(EventConfigBuilderChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ event -> resetData("onEventConfigBuilderChange", event) }, fabricPrivacy::logException)
.subscribe({ event -> resetDataAndRunCalculation("onEventConfigBuilderChange", event) }, fabricPrivacy::logException)
// EventNewBasalProfile
disposable += rxBus
.toObservable(EventNewBasalProfile::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ event -> resetData("onNewProfile", event) }, fabricPrivacy::logException)
.subscribe({ event -> resetDataAndRunCalculation("onNewProfile", event) }, fabricPrivacy::logException)
// EventPreferenceChange
disposable += rxBus
.toObservable(EventPreferenceChange::class.java)
@ -115,7 +105,7 @@ open class IobCobCalculatorPlugin @Inject constructor(
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)) {
resetData("onEventPreferenceChange", event)
resetDataAndRunCalculation("onEventPreferenceChange", event)
}
}, fabricPrivacy::logException)
// EventAppInitialized
@ -135,217 +125,19 @@ open class IobCobCalculatorPlugin @Inject constructor(
super.onStop()
}
override fun getBgReadingsDataTableCopy(): List<GlucoseValue> = bgReadings.toList()
override fun getBucketedDataTableCopy(): MutableList<InMemoryGlucoseValue>? = bucketedData?.toMutableList()
override fun getAutosensDataTable(): LongSparseArray<AutosensData> = autosensDataTable
private 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(reason: String, event: Event?) {
private fun resetDataAndRunCalculation(reason: String, event: Event?) {
stopCalculation(reason)
synchronized(dataLock) {
aapsLogger.debug(LTag.AUTOSENS, "Invalidating cached data because of $reason.")
synchronized(dataLock) {
iobTable = LongSparseArray()
autosensDataTable = LongSparseArray()
basalDataTable = LongSparseArray()
absIobTable = LongSparseArray()
}
}
clearCache()
ads.reset()
runCalculation(reason, System.currentTimeMillis(), bgDataReload = false, limitDataToOldestAvailable = true, cause = event)
}
fun createBucketedData() {
val fiveMinData = isAbout5minData
if (lastUsed5minCalculation != null && lastUsed5minCalculation != fiveMinData) {
// changing mode => clear cache
aapsLogger.debug("Invalidating cached data because of changed mode.")
resetData("changed mode", null)
fun clearCache() {
synchronized(dataLock) {
aapsLogger.debug(LTag.AUTOSENS, "Clearing cached data.")
iobTable = LongSparseArray()
basalDataTable = LongSparseArray()
}
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
}
private fun oldestDataAvailable(): Long {
@ -375,17 +167,9 @@ open class IobCobCalculatorPlugin @Inject constructor(
return getBGDataFrom
}
override 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 {
override fun calculateFromTreatmentsAndTemps(fromTime: Long, profile: Profile): IobTotal {
val now = System.currentTimeMillis()
val time = roundUpTime(fromTime)
val time = ads.roundUpTime(fromTime)
val cacheHit = iobTable[time]
if (time < now && cacheHit != null) {
//og.debug(">>> calculateFromTreatmentsAndTemps Cache hit " + new Date(time).toLocaleString());
@ -414,10 +198,10 @@ open class IobCobCalculatorPlugin @Inject constructor(
return iobTotal
}
override fun calculateAbsInsulinFromTreatmentsAndTempsSynchronized(fromTime: Long): IobTotal {
override fun calculateAbsInsulinFromTreatmentsAndTemps(fromTime: Long): IobTotal {
synchronized(dataLock) {
val now = System.currentTimeMillis()
val time = roundUpTime(fromTime)
val time = ads.roundUpTime(fromTime)
val cacheHit = absIobTable[time]
if (time < now && cacheHit != null) {
//log.debug(">>> calculateFromTreatmentsAndTemps Cache hit " + new Date(time).toLocaleString());
@ -457,18 +241,10 @@ open class IobCobCalculatorPlugin @Inject constructor(
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
}
override fun getBasalData(profile: Profile, fromTime: Long): BasalData {
synchronized(dataLock) {
val now = System.currentTimeMillis()
val time = roundUpTime(fromTime)
val time = ads.roundUpTime(fromTime)
var retVal = basalDataTable[time]
if (retVal == null) {
//log.debug(">>> getBasalData Cache miss " + new Date(time).toLocaleString());
@ -490,19 +266,6 @@ open class IobCobCalculatorPlugin @Inject constructor(
}
}
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]
}
}
override fun getLastAutosensDataWithWaitForCalculationFinish(reason: String): AutosensData? {
if (thread?.isAlive == true) {
aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA is waiting for calculation thread: $reason")
@ -512,13 +275,13 @@ open class IobCobCalculatorPlugin @Inject constructor(
}
aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA finished waiting for calculation thread: $reason")
}
synchronized(dataLock) { return getLastAutosensData(reason) }
return ads.getLastAutosensData(reason, aapsLogger, dateUtil)
}
override fun getCobInfo(waitForCalculationFinish: Boolean, reason: String): CobInfo {
val autosensData =
if (waitForCalculationFinish) getLastAutosensDataWithWaitForCalculationFinish(reason)
else getLastAutosensData(reason)
else ads.getLastAutosensData(reason, aapsLogger, dateUtil)
var displayCob: Double? = null
var futureCarbs = 0.0
val now = dateUtil.now()
@ -526,7 +289,7 @@ open class IobCobCalculatorPlugin @Inject constructor(
if (autosensData != null) {
displayCob = autosensData.cob
carbs.forEach { carb ->
if (roundUpTime(carb.timestamp) > roundUpTime(autosensData.time) && carb.timestamp <= now) {
if (ads.roundUpTime(carb.timestamp) > ads.roundUpTime(autosensData.time) && carb.timestamp <= now) {
displayCob += carb.amount
}
}
@ -536,52 +299,6 @@ open class IobCobCalculatorPlugin @Inject constructor(
return CobInfo(displayCob, futureCarbs)
}
override 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
}
override 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 =
if (autosensDataTable.size() > 0) dateUtil.dateAndTimeAndSecondsString(autosensDataTable.valueAt(autosensDataTable.size() - 1).time)
else "autosensDataTable empty"
override fun getMealDataWithWaitingForCalculationFinish(): MealData {
val result = MealData()
val now = System.currentTimeMillis()
@ -614,12 +331,12 @@ open class IobCobCalculatorPlugin @Inject constructor(
override fun calculateIobArrayInDia(profile: Profile): Array<IobTotal> {
// predict IOB out to DIA plus 30m
var time = System.currentTimeMillis()
time = roundUpTime(time)
time = ads.roundUpTime(time)
val len = ((profile.dia * 60 + 30) / 5).toInt()
val array = Array(len) { IobTotal(0) }
for ((pos, i) in (0 until len).withIndex()) {
val t = time + i * 5 * 60000
val iob = calculateFromTreatmentsAndTempsSynchronized(t, profile)
val iob = calculateFromTreatmentsAndTemps(t, profile)
array[pos] = iob
}
return array
@ -632,7 +349,7 @@ open class IobCobCalculatorPlugin @Inject constructor(
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)
val iob = calculateFromTreatmentsAndTemps(t, lastAutosensResult, exercise_mode, half_basal_exercise_target, isTempTarget)
array[pos] = iob
}
return array
@ -649,10 +366,6 @@ open class IobCobCalculatorPlugin @Inject constructor(
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
@ -700,14 +413,6 @@ open class IobCobCalculatorPlugin @Inject constructor(
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)))
@ -716,39 +421,12 @@ open class IobCobCalculatorPlugin @Inject constructor(
break
}
}
ads.newHistoryData(time, aapsLogger, dateUtil)
}
runCalculation("onEventNewHistoryData", System.currentTimeMillis(), bgDataReload, true, event)
//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
*/
override fun lastBg(): GlucoseValue? {
val bgList = bgReadings
for (i in bgList.indices) if (bgList[i].value >= 39) return bgList[i]
return null
}
override fun actualBg(): GlucoseValue? {
val lastBg = lastBg() ?: return null
return if (lastBg.timestamp > System.currentTimeMillis() - T.mins(9).msecs()) lastBg else null
}
// roundup to whole minute
fun roundUpTime(time: Long): Long {
return if (time % 60000 == 0L) time else (time / 60000 + 1) * 60000
}
override fun convertToJSONArray(iobArray: Array<IobTotal>): JSONArray {
val array = JSONArray()
for (i in iobArray.indices) {

View file

@ -10,6 +10,8 @@ import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.events.Event
import info.nightscout.androidaps.events.EventAutosensCalculationFinished
import info.nightscout.androidaps.extensions.target
import info.nightscout.androidaps.interfaces.ActivePluginProvider
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
@ -21,9 +23,12 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress
import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin
import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin
import info.nightscout.androidaps.utils.*
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DecimalFormatter
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.Profiler
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.extensions.target
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
import java.util.*
@ -51,6 +56,7 @@ class IobCobOref1Thread internal constructor(
@Inject lateinit var context: Context
@Inject lateinit var sensitivityAAPSPlugin: SensitivityAAPSPlugin
@Inject lateinit var sensitivityWeightedAveragePlugin: SensitivityWeightedAveragePlugin
@Inject lateinit var activePlugin: ActivePluginProvider
@Inject lateinit var buildHelper: BuildHelper
@Inject lateinit var profiler: Profiler
@Inject lateinit var fabricPrivacy: FabricPrivacy
@ -75,247 +81,248 @@ class IobCobOref1Thread internal constructor(
}
//log.debug("Locking calculateSensitivityData");
val oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable)
synchronized(iobCobCalculatorPlugin.dataLock) {
aapsLogger.debug("XXXXXXXXXXXXX START $from")
if (bgDataReload) {
iobCobCalculatorPlugin.loadBgData(end)
iobCobCalculatorPlugin.createBucketedData()
}
val bucketedData = iobCobCalculatorPlugin.getBucketedDataTableCopy()
val autosensDataTable = iobCobCalculatorPlugin.getAutosensDataTable()
if (bucketedData == null || bucketedData.size < 3) {
aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No bucketed data available): $from")
aapsLogger.debug("XXXXXXXXXXXXX START $from")
if (bgDataReload) {
iobCobCalculatorPlugin.ads.loadBgData(end, repository, aapsLogger, dateUtil)
iobCobCalculatorPlugin.clearCache()
}
// work on local copy and set back when finished
val ads = iobCobCalculatorPlugin.ads.clone()
val bucketedData = ads.bucketedData
val autosensDataTable = ads.autosensDataTable
if (bucketedData == null || bucketedData.size < 3) {
aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No bucketed data available): $from")
return
}
val prevDataTime = ads.roundUpTime(bucketedData[bucketedData.size - 3].timestamp)
aapsLogger.debug(LTag.AUTOSENS, "Prev data time: " + dateUtil.dateAndTimeString(prevDataTime))
var previous = autosensDataTable[prevDataTime]
// start from oldest to be able sub cob
for (i in bucketedData.size - 4 downTo 0) {
val progress = i.toString() + if (buildHelper.isDev()) " ($from)" else ""
rxBus.send(EventIobCalculationProgress(progress))
if (iobCobCalculatorPlugin.stopCalculationTrigger) {
iobCobCalculatorPlugin.stopCalculationTrigger = false
aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): $from")
aapsLogger.debug("XXXXXXXXXXXXX STOP")
return
}
val prevDataTime = iobCobCalculatorPlugin.roundUpTime(bucketedData[bucketedData.size - 3].timestamp)
aapsLogger.debug(LTag.AUTOSENS, "Prev data time: " + dateUtil.dateAndTimeString(prevDataTime))
var previous = autosensDataTable[prevDataTime]
// start from oldest to be able sub cob
for (i in bucketedData.size - 4 downTo 0) {
val progress = i.toString() + if (buildHelper.isDev()) " ($from)" else ""
rxBus.send(EventIobCalculationProgress(progress))
if (iobCobCalculatorPlugin.stopCalculationTrigger) {
iobCobCalculatorPlugin.stopCalculationTrigger = false
aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): $from")
aapsLogger.debug("XXXXXXXXXXXXX STOP")
return
}
// check if data already exists
var bgTime = bucketedData[i].timestamp
bgTime = iobCobCalculatorPlugin.roundUpTime(bgTime)
if (bgTime > iobCobCalculatorPlugin.roundUpTime(dateUtil.now())) continue
var existing: AutosensData?
if (autosensDataTable[bgTime].also { existing = it } != null) {
previous = existing
continue
}
val profile = profileFunction.getProfile(bgTime)
if (profile == null) {
aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (no profile): $from")
return // profile not set yet
}
aapsLogger.debug("XXXXXXXXXXXXX FOR $bgTime ${dateUtil.dateAndTimeString(bgTime)}")
aapsLogger.debug(LTag.AUTOSENS, "Processing calculation thread: " + from + " (" + i + "/" + bucketedData.size + ")")
val sens = profile.getIsfMgdl(bgTime)
val autosensData = AutosensData(injector)
autosensData.time = bgTime
if (previous != null) autosensData.activeCarbsList = previous.cloneCarbsList() else autosensData.activeCarbsList = ArrayList()
//console.error(bgTime , bucketed_data[i].glucose);
var avgDelta: Double
var delta: Double
val bg: Double = bucketedData[i].value
if (bg < 39 || bucketedData[i + 3].value < 39) {
aapsLogger.error("! value < 39")
continue
}
autosensData.bg = bg
delta = bg - bucketedData[i + 1].value
avgDelta = (bg - bucketedData[i + 3].value) / 3
val iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTemps(bgTime, profile)
val bgi = -iob.activity * sens * 5
val deviation = delta - bgi
val avgDeviation = ((avgDelta - bgi) * 1000).roundToLong() / 1000.0
var slopeFromMaxDeviation = 0.0
var slopeFromMinDeviation = 999.0
// https://github.com/openaps/oref0/blob/master/lib/determine-basal/cob-autosens.js#L169
if (i < bucketedData.size - 16) { // we need 1h of data to calculate minDeviationSlope
@Suppress("UNUSED_VARIABLE") var maxDeviation = 0.0
@Suppress("UNUSED_VARIABLE") var minDeviation = 999.0
val hourAgo = bgTime + 10 * 1000 - 60 * 60 * 1000L
val hourAgoData = iobCobCalculatorPlugin.getAutosensData(hourAgo)
if (hourAgoData != null) {
val initialIndex = autosensDataTable.indexOfKey(hourAgoData.time)
aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketedData.size + " i=" + i + " hourAgoData=" + hourAgoData.toString())
var past = 1
try {
while (past < 12) {
val ad = autosensDataTable.valueAt(initialIndex + past)
aapsLogger.debug(LTag.AUTOSENS, ">>>>> past=" + past + " ad=" + ad?.toString())
if (ad == null) {
aapsLogger.debug(LTag.AUTOSENS, autosensDataTable.toString())
aapsLogger.debug(LTag.AUTOSENS, bucketedData.toString())
//aapsLogger.debug(LTag.AUTOSENS, iobCobCalculatorPlugin.getBgReadingsDataTable().toString())
val notification = Notification(Notification.SEND_LOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW)
rxBus.send(EventNewNotification(notification))
sp.putBoolean("log_AUTOSENS", true)
break
}
// let it here crash on NPE to get more data as i cannot reproduce this bug
val deviationSlope = (ad.avgDeviation - avgDeviation) / (ad.time - bgTime) * 1000 * 60 * 5
if (ad.avgDeviation > maxDeviation) {
slopeFromMaxDeviation = min(0.0, deviationSlope)
maxDeviation = ad.avgDeviation
}
if (ad.avgDeviation < minDeviation) {
slopeFromMinDeviation = max(0.0, deviationSlope)
minDeviation = ad.avgDeviation
}
past++
}
} catch (e: Exception) {
aapsLogger.error("Unhandled exception", e)
fabricPrivacy.logException(e)
aapsLogger.debug(autosensDataTable.toString())
aapsLogger.debug(bucketedData.toString())
//aapsLogger.debug(iobCobCalculatorPlugin.getBgReadingsDataTable().toString())
val notification = Notification(Notification.SEND_LOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW)
rxBus.send(EventNewNotification(notification))
sp.putBoolean("log_AUTOSENS", true)
break
}
} else {
aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketedData.size + " i=" + i + " hourAgoData=" + "null")
}
}
val recentCarbTreatments = repository.getCarbsDataFromTimeToTimeExpanded(bgTime - T.mins(5).msecs(), bgTime, true).blockingGet()
for (recentCarbTreatment in recentCarbTreatments) {
aapsLogger.debug("XXXXXXXXXXXXX $bgTime ${dateUtil.dateAndTimeString(bgTime)} $recentCarbTreatment")
autosensData.carbsFromBolus += recentCarbTreatment.amount
val isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled()
autosensData.activeCarbsList.add(autosensData.CarbsInPast(recentCarbTreatment, isAAPSOrWeighted))
autosensData.pastSensitivity += "[" + DecimalFormatter.to0Decimal(recentCarbTreatment.amount) + "g]"
}
// if we are absorbing carbs
if (previous != null && previous.cob > 0) {
// calculate sum of min carb impact from all active treatments
val totalMinCarbsImpact = sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact)
// figure out how many carbs that represents
// but always assume at least 3mg/dL/5m (default) absorption per active treatment
val ci = max(deviation, totalMinCarbsImpact)
if (ci != deviation) autosensData.failoverToMinAbsorbtionRate = true
autosensData.absorbed = ci * profile.getIc(bgTime) / sens
// and add that to the running total carbsAbsorbed
autosensData.cob = max(previous.cob - autosensData.absorbed, 0.0)
autosensData.mealCarbs = previous.mealCarbs
autosensData.substractAbosorbedCarbs()
autosensData.usedMinCarbsImpact = totalMinCarbsImpact
autosensData.absorbing = previous.absorbing
autosensData.mealStartCounter = previous.mealStartCounter
autosensData.type = previous.type
autosensData.uam = previous.uam
}
val isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled()
autosensData.removeOldCarbs(bgTime, isAAPSOrWeighted)
autosensData.cob += autosensData.carbsFromBolus
autosensData.mealCarbs += autosensData.carbsFromBolus
autosensData.deviation = deviation
autosensData.bgi = bgi
autosensData.delta = delta
autosensData.avgDelta = avgDelta
autosensData.avgDeviation = avgDeviation
autosensData.slopeFromMaxDeviation = slopeFromMaxDeviation
autosensData.slopeFromMinDeviation = slopeFromMinDeviation
// If mealCOB is zero but all deviations since hitting COB=0 are positive, exclude from autosens
if (autosensData.cob > 0 || autosensData.absorbing || autosensData.mealCarbs > 0) {
autosensData.absorbing = deviation > 0
// stop excluding positive deviations as soon as mealCOB=0 if meal has been absorbing for >5h
if (autosensData.mealStartCounter > 60 && autosensData.cob < 0.5) {
autosensData.absorbing = false
}
if (!autosensData.absorbing && autosensData.cob < 0.5) {
autosensData.mealCarbs = 0.0
}
// check previous "type" value, and if it wasn't csf, set a mealAbsorption start flag
if (autosensData.type != "csf") {
// process.stderr.write("(");
autosensData.mealStartCounter = 0
}
autosensData.mealStartCounter++
autosensData.type = "csf"
} else {
// check previous "type" value, and if it was csf, set a mealAbsorption end flag
val currentBasal = profile.getBasal(bgTime)
// always exclude the first 45m after each carb entry
//if (iob.iob > currentBasal || uam ) {
if (iob.iob > 2 * currentBasal || autosensData.uam || autosensData.mealStartCounter < 9) {
autosensData.mealStartCounter++
autosensData.uam = deviation > 0
autosensData.type = "uam"
} else {
autosensData.type = "non-meal"
}
}
// Exclude meal-related deviations (carb absorption) from autosens
when (autosensData.type) {
"non-meal" -> {
when {
abs(deviation) < Constants.DEVIATION_TO_BE_EQUAL -> {
autosensData.pastSensitivity += "="
autosensData.validDeviation = true
}
deviation > 0 -> {
autosensData.pastSensitivity += "+"
autosensData.validDeviation = true
}
else -> {
autosensData.pastSensitivity += "-"
autosensData.validDeviation = true
}
}
}
"uam" -> {
autosensData.pastSensitivity += "u"
}
else -> {
autosensData.pastSensitivity += "x"
}
}
// add an extra negative deviation if a high temp target is running and exercise mode is set
// TODO AS-FIX
@Suppress("SimplifyBooleanWithConstants")
if (false && sp.getBoolean(R.string.key_high_temptarget_raises_sensitivity, SMBDefaults.high_temptarget_raises_sensitivity)) {
val tempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet()
if (tempTarget is ValueWrapper.Existing && tempTarget.value.target() >= 100) {
autosensData.extraDeviation.add(-(tempTarget.value.target() - 100) / 20)
}
}
// add one neutral deviation every 2 hours to help decay over long exclusion periods
val calendar = GregorianCalendar()
calendar.timeInMillis = bgTime
val min = calendar[Calendar.MINUTE]
val hours = calendar[Calendar.HOUR_OF_DAY]
if (min in 0..4 && hours % 2 == 0) autosensData.extraDeviation.add(0.0)
previous = autosensData
if (bgTime < dateUtil.now()) autosensDataTable.put(bgTime, autosensData)
aapsLogger.debug(LTag.AUTOSENS, "Running detectSensitivity from: " + dateUtil.dateAndTimeString(oldestTimeWithData) + " to: " + dateUtil.dateAndTimeString(bgTime) + " lastDataTime:" + iobCobCalculatorPlugin.lastDataTime())
val sensitivity = iobCobCalculatorPlugin.detectSensitivityWithLock(oldestTimeWithData, bgTime)
aapsLogger.debug(LTag.AUTOSENS, "Sensitivity result: $sensitivity")
autosensData.autosensResult = sensitivity
aapsLogger.debug(LTag.AUTOSENS, autosensData.toString())
// check if data already exists
var bgTime = bucketedData[i].timestamp
bgTime = ads.roundUpTime(bgTime)
if (bgTime > ads.roundUpTime(dateUtil.now())) continue
var existing: AutosensData?
if (autosensDataTable[bgTime].also { existing = it } != null) {
previous = existing
continue
}
val profile = profileFunction.getProfile(bgTime)
if (profile == null) {
aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (no profile): $from")
return // profile not set yet
}
aapsLogger.debug("XXXXXXXXXXXXX FOR $bgTime ${dateUtil.dateAndTimeString(bgTime)}")
aapsLogger.debug(LTag.AUTOSENS, "Processing calculation thread: " + from + " (" + i + "/" + bucketedData.size + ")")
val sens = profile.getIsfMgdl(bgTime)
val autosensData = AutosensData(injector)
autosensData.time = bgTime
if (previous != null) autosensData.activeCarbsList = previous.cloneCarbsList() else autosensData.activeCarbsList = ArrayList()
//console.error(bgTime , bucketed_data[i].glucose);
var avgDelta: Double
var delta: Double
val bg: Double = bucketedData[i].value
if (bg < 39 || bucketedData[i + 3].value < 39) {
aapsLogger.error("! value < 39")
continue
}
autosensData.bg = bg
delta = bg - bucketedData[i + 1].value
avgDelta = (bg - bucketedData[i + 3].value) / 3
val iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTemps(bgTime, profile)
val bgi = -iob.activity * sens * 5
val deviation = delta - bgi
val avgDeviation = ((avgDelta - bgi) * 1000).roundToLong() / 1000.0
var slopeFromMaxDeviation = 0.0
var slopeFromMinDeviation = 999.0
// https://github.com/openaps/oref0/blob/master/lib/determine-basal/cob-autosens.js#L169
if (i < bucketedData.size - 16) { // we need 1h of data to calculate minDeviationSlope
@Suppress("UNUSED_VARIABLE") var maxDeviation = 0.0
@Suppress("UNUSED_VARIABLE") var minDeviation = 999.0
val hourAgo = bgTime + 10 * 1000 - 60 * 60 * 1000L
val hourAgoData = ads.getAutosensDataAtTime(hourAgo)
if (hourAgoData != null) {
val initialIndex = autosensDataTable.indexOfKey(hourAgoData.time)
aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketedData.size + " i=" + i + " hourAgoData=" + hourAgoData.toString())
var past = 1
try {
while (past < 12) {
val ad = autosensDataTable.valueAt(initialIndex + past)
aapsLogger.debug(LTag.AUTOSENS, ">>>>> past=" + past + " ad=" + ad?.toString())
if (ad == null) {
aapsLogger.debug(LTag.AUTOSENS, autosensDataTable.toString())
aapsLogger.debug(LTag.AUTOSENS, bucketedData.toString())
//aapsLogger.debug(LTag.AUTOSENS, iobCobCalculatorPlugin.getBgReadingsDataTable().toString())
val notification = Notification(Notification.SEND_LOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW)
rxBus.send(EventNewNotification(notification))
sp.putBoolean("log_AUTOSENS", true)
break
}
// let it here crash on NPE to get more data as i cannot reproduce this bug
val deviationSlope = (ad.avgDeviation - avgDeviation) / (ad.time - bgTime) * 1000 * 60 * 5
if (ad.avgDeviation > maxDeviation) {
slopeFromMaxDeviation = min(0.0, deviationSlope)
maxDeviation = ad.avgDeviation
}
if (ad.avgDeviation < minDeviation) {
slopeFromMinDeviation = max(0.0, deviationSlope)
minDeviation = ad.avgDeviation
}
past++
}
} catch (e: Exception) {
aapsLogger.error("Unhandled exception", e)
fabricPrivacy.logException(e)
aapsLogger.debug(autosensDataTable.toString())
aapsLogger.debug(bucketedData.toString())
//aapsLogger.debug(iobCobCalculatorPlugin.getBgReadingsDataTable().toString())
val notification = Notification(Notification.SEND_LOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW)
rxBus.send(EventNewNotification(notification))
sp.putBoolean("log_AUTOSENS", true)
break
}
} else {
aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketedData.size + " i=" + i + " hourAgoData=" + "null")
}
}
val recentCarbTreatments = repository.getCarbsDataFromTimeToTimeExpanded(bgTime - T.mins(5).msecs(), bgTime, true).blockingGet()
for (recentCarbTreatment in recentCarbTreatments) {
aapsLogger.debug("XXXXXXXXXXXXX $bgTime ${dateUtil.dateAndTimeString(bgTime)} $recentCarbTreatment")
autosensData.carbsFromBolus += recentCarbTreatment.amount
val isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled()
autosensData.activeCarbsList.add(autosensData.CarbsInPast(recentCarbTreatment, isAAPSOrWeighted))
autosensData.pastSensitivity += "[" + DecimalFormatter.to0Decimal(recentCarbTreatment.amount) + "g]"
}
// if we are absorbing carbs
if (previous != null && previous.cob > 0) {
// calculate sum of min carb impact from all active treatments
val totalMinCarbsImpact = sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact)
// figure out how many carbs that represents
// but always assume at least 3mg/dL/5m (default) absorption per active treatment
val ci = max(deviation, totalMinCarbsImpact)
if (ci != deviation) autosensData.failoverToMinAbsorbtionRate = true
autosensData.absorbed = ci * profile.getIc(bgTime) / sens
// and add that to the running total carbsAbsorbed
autosensData.cob = max(previous.cob - autosensData.absorbed, 0.0)
autosensData.mealCarbs = previous.mealCarbs
autosensData.substractAbosorbedCarbs()
autosensData.usedMinCarbsImpact = totalMinCarbsImpact
autosensData.absorbing = previous.absorbing
autosensData.mealStartCounter = previous.mealStartCounter
autosensData.type = previous.type
autosensData.uam = previous.uam
}
val isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled()
autosensData.removeOldCarbs(bgTime, isAAPSOrWeighted)
autosensData.cob += autosensData.carbsFromBolus
autosensData.mealCarbs += autosensData.carbsFromBolus
autosensData.deviation = deviation
autosensData.bgi = bgi
autosensData.delta = delta
autosensData.avgDelta = avgDelta
autosensData.avgDeviation = avgDeviation
autosensData.slopeFromMaxDeviation = slopeFromMaxDeviation
autosensData.slopeFromMinDeviation = slopeFromMinDeviation
// If mealCOB is zero but all deviations since hitting COB=0 are positive, exclude from autosens
if (autosensData.cob > 0 || autosensData.absorbing || autosensData.mealCarbs > 0) {
autosensData.absorbing = deviation > 0
// stop excluding positive deviations as soon as mealCOB=0 if meal has been absorbing for >5h
if (autosensData.mealStartCounter > 60 && autosensData.cob < 0.5) {
autosensData.absorbing = false
}
if (!autosensData.absorbing && autosensData.cob < 0.5) {
autosensData.mealCarbs = 0.0
}
// check previous "type" value, and if it wasn't csf, set a mealAbsorption start flag
if (autosensData.type != "csf") {
// process.stderr.write("(");
autosensData.mealStartCounter = 0
}
autosensData.mealStartCounter++
autosensData.type = "csf"
} else {
// check previous "type" value, and if it was csf, set a mealAbsorption end flag
val currentBasal = profile.getBasal(bgTime)
// always exclude the first 45m after each carb entry
//if (iob.iob > currentBasal || uam ) {
if (iob.iob > 2 * currentBasal || autosensData.uam || autosensData.mealStartCounter < 9) {
autosensData.mealStartCounter++
autosensData.uam = deviation > 0
autosensData.type = "uam"
} else {
autosensData.type = "non-meal"
}
}
// Exclude meal-related deviations (carb absorption) from autosens
when (autosensData.type) {
"non-meal" -> {
when {
abs(deviation) < Constants.DEVIATION_TO_BE_EQUAL -> {
autosensData.pastSensitivity += "="
autosensData.validDeviation = true
}
deviation > 0 -> {
autosensData.pastSensitivity += "+"
autosensData.validDeviation = true
}
else -> {
autosensData.pastSensitivity += "-"
autosensData.validDeviation = true
}
}
}
"uam" -> {
autosensData.pastSensitivity += "u"
}
else -> {
autosensData.pastSensitivity += "x"
}
}
// add an extra negative deviation if a high temp target is running and exercise mode is set
// TODO AS-FIX
@Suppress("SimplifyBooleanWithConstants")
if (false && sp.getBoolean(R.string.key_high_temptarget_raises_sensitivity, SMBDefaults.high_temptarget_raises_sensitivity)) {
val tempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet()
if (tempTarget is ValueWrapper.Existing && tempTarget.value.target() >= 100) {
autosensData.extraDeviation.add(-(tempTarget.value.target() - 100) / 20)
}
}
// add one neutral deviation every 2 hours to help decay over long exclusion periods
val calendar = GregorianCalendar()
calendar.timeInMillis = bgTime
val min = calendar[Calendar.MINUTE]
val hours = calendar[Calendar.HOUR_OF_DAY]
if (min in 0..4 && hours % 2 == 0) autosensData.extraDeviation.add(0.0)
previous = autosensData
if (bgTime < dateUtil.now()) autosensDataTable.put(bgTime, autosensData)
aapsLogger.debug(LTag.AUTOSENS, "Running detectSensitivity from: " + dateUtil.dateAndTimeString(oldestTimeWithData) + " to: " + dateUtil.dateAndTimeString(bgTime) + " lastDataTime:" + ads.lastDataTime(dateUtil))
val sensitivity = activePlugin.activeSensitivity.detectSensitivity(ads, oldestTimeWithData, bgTime)
aapsLogger.debug(LTag.AUTOSENS, "Sensitivity result: $sensitivity")
autosensData.autosensResult = sensitivity
aapsLogger.debug(LTag.AUTOSENS, autosensData.toString())
}
iobCobCalculatorPlugin.ads = ads
Thread {
SystemClock.sleep(1000)
rxBus.send(EventAutosensCalculationFinished(cause))
@ -324,7 +331,6 @@ class IobCobOref1Thread internal constructor(
mWakeLock?.release()
rxBus.send(EventIobCalculationProgress(""))
aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: $from")
aapsLogger.debug(LTag.AUTOSENS, "Midnights: " + MidnightTime.log())
profiler.log(LTag.AUTOSENS, "IobCobOref1Thread", start)
}
}

View file

@ -9,6 +9,7 @@ import info.nightscout.androidaps.R
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.events.Event
import info.nightscout.androidaps.events.EventAutosensCalculationFinished
import info.nightscout.androidaps.interfaces.ActivePluginProvider
import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.logging.AAPSLogger
@ -21,7 +22,11 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress
import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin
import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin
import info.nightscout.androidaps.utils.*
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DecimalFormatter
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.Profiler
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
@ -50,6 +55,7 @@ class IobCobThread @Inject internal constructor(
@Inject lateinit var context: Context
@Inject lateinit var sensitivityAAPSPlugin: SensitivityAAPSPlugin
@Inject lateinit var sensitivityWeightedAveragePlugin: SensitivityWeightedAveragePlugin
@Inject lateinit var activePlugin: ActivePluginProvider
@Inject lateinit var buildHelper: BuildHelper
@Inject lateinit var profiler: Profiler
@Inject lateinit var fabricPrivacy: FabricPrivacy
@ -74,191 +80,192 @@ class IobCobThread @Inject internal constructor(
}
//log.debug("Locking calculateSensitivityData");
val oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable)
synchronized(iobCobCalculatorPlugin.dataLock) {
if (bgDataReload) {
iobCobCalculatorPlugin.loadBgData(end)
iobCobCalculatorPlugin.createBucketedData()
}
val bucketedData = iobCobCalculatorPlugin.getBucketedDataTableCopy()
val autosensDataTable = iobCobCalculatorPlugin.getAutosensDataTable()
if (bucketedData == null || bucketedData.size < 3) {
aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No bucketed data available): $from")
if (bgDataReload) {
iobCobCalculatorPlugin.ads.loadBgData(end, repository, aapsLogger, dateUtil)
iobCobCalculatorPlugin.clearCache()
}
// work on local copy and set back when finished
val ads = iobCobCalculatorPlugin.ads.clone()
val bucketedData = ads.bucketedData
val autosensDataTable = ads.autosensDataTable
if (bucketedData == null || bucketedData.size < 3) {
aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No bucketed data available): $from")
return
}
val prevDataTime = ads.roundUpTime(bucketedData[bucketedData.size - 3].timestamp)
aapsLogger.debug(LTag.AUTOSENS, "Prev data time: " + dateUtil.dateAndTimeString(prevDataTime))
var previous = autosensDataTable[prevDataTime]
// start from oldest to be able sub cob
for (i in bucketedData.size - 4 downTo 0) {
val progress = i.toString() + if (buildHelper.isDev()) " ($from)" else ""
rxBus.send(EventIobCalculationProgress(progress))
if (iobCobCalculatorPlugin.stopCalculationTrigger) {
iobCobCalculatorPlugin.stopCalculationTrigger = false
aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): $from")
return
}
val prevDataTime = iobCobCalculatorPlugin.roundUpTime(bucketedData[bucketedData.size - 3].timestamp)
aapsLogger.debug(LTag.AUTOSENS, "Prev data time: " + dateUtil.dateAndTimeString(prevDataTime))
var previous = autosensDataTable[prevDataTime]
// start from oldest to be able sub cob
for (i in bucketedData.size - 4 downTo 0) {
val progress = i.toString() + if (buildHelper.isDev()) " ($from)" else ""
rxBus.send(EventIobCalculationProgress(progress))
if (iobCobCalculatorPlugin.stopCalculationTrigger) {
iobCobCalculatorPlugin.stopCalculationTrigger = false
aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): $from")
return
}
// check if data already exists
var bgTime = bucketedData[i].timestamp
bgTime = iobCobCalculatorPlugin.roundUpTime(bgTime)
if (bgTime > iobCobCalculatorPlugin.roundUpTime(dateUtil.now())) continue
var existing: AutosensData?
if (autosensDataTable[bgTime].also { existing = it } != null) {
previous = existing
continue
}
val profile = profileFunction.getProfile(bgTime)
if (profile == null) {
aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (no profile): $from")
return // profile not set yet
}
aapsLogger.debug(LTag.AUTOSENS, "Processing calculation thread: " + from + " (" + i + "/" + bucketedData.size + ")")
val sens = profile.getIsfMgdl(bgTime)
val autosensData = AutosensData(injector)
autosensData.time = bgTime
if (previous != null) autosensData.activeCarbsList = previous.cloneCarbsList() else autosensData.activeCarbsList = ArrayList()
// check if data already exists
var bgTime = bucketedData[i].timestamp
bgTime = ads.roundUpTime(bgTime)
if (bgTime > ads.roundUpTime(dateUtil.now())) continue
var existing: AutosensData?
if (autosensDataTable[bgTime].also { existing = it } != null) {
previous = existing
continue
}
val profile = profileFunction.getProfile(bgTime)
if (profile == null) {
aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (no profile): $from")
return // profile not set yet
}
aapsLogger.debug(LTag.AUTOSENS, "Processing calculation thread: " + from + " (" + i + "/" + bucketedData.size + ")")
val sens = profile.getIsfMgdl(bgTime)
val autosensData = AutosensData(injector)
autosensData.time = bgTime
if (previous != null) autosensData.activeCarbsList = previous.cloneCarbsList() else autosensData.activeCarbsList = ArrayList()
//console.error(bgTime , bucketed_data[i].glucose);
var avgDelta: Double
var delta: Double
val bg: Double = bucketedData[i].value
if (bg < 39 || bucketedData[i + 3].value < 39) {
aapsLogger.error("! value < 39")
continue
}
autosensData.bg = bg
delta = bg - bucketedData[i + 1].value
avgDelta = (bg - bucketedData[i + 3].value) / 3
val iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTemps(bgTime, profile)
val bgi = -iob.activity * sens * 5
val deviation = delta - bgi
val avgDeviation = ((avgDelta - bgi) * 1000).roundToLong() / 1000.0
var slopeFromMaxDeviation = 0.0
var slopeFromMinDeviation = 999.0
//console.error(bgTime , bucketed_data[i].glucose);
var avgDelta: Double
var delta: Double
val bg: Double = bucketedData[i].value
if (bg < 39 || bucketedData[i + 3].value < 39) {
aapsLogger.error("! value < 39")
continue
}
autosensData.bg = bg
delta = bg - bucketedData[i + 1].value
avgDelta = (bg - bucketedData[i + 3].value) / 3
val iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTemps(bgTime, profile)
val bgi = -iob.activity * sens * 5
val deviation = delta - bgi
val avgDeviation = ((avgDelta - bgi) * 1000).roundToLong() / 1000.0
var slopeFromMaxDeviation = 0.0
var slopeFromMinDeviation = 999.0
// https://github.com/openaps/oref0/blob/master/lib/determine-basal/cob-autosens.js#L169
if (i < bucketedData.size - 16) { // we need 1h of data to calculate minDeviationSlope
@Suppress("UNUSED_VARIABLE") var maxDeviation = 0.0
@Suppress("UNUSED_VARIABLE") var minDeviation = 999.0
val hourAgo = bgTime + 10 * 1000 - 60 * 60 * 1000L
val hourAgoData = iobCobCalculatorPlugin.getAutosensData(hourAgo)
if (hourAgoData != null) {
val initialIndex = autosensDataTable.indexOfKey(hourAgoData.time)
aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketedData.size + " i=" + i + " hourAgoData=" + hourAgoData.toString())
var past = 1
try {
while (past < 12) {
val ad = autosensDataTable.valueAt(initialIndex + past)
aapsLogger.debug(LTag.AUTOSENS, ">>>>> past=" + past + " ad=" + ad?.toString())
if (ad == null) {
aapsLogger.debug(LTag.AUTOSENS, autosensDataTable.toString())
aapsLogger.debug(LTag.AUTOSENS, bucketedData.toString())
//aapsLogger.debug(LTag.AUTOSENS, iobCobCalculatorPlugin.getBgReadingsDataTable().toString())
val notification = Notification(Notification.SEND_LOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW)
rxBus.send(EventNewNotification(notification))
sp.putBoolean("log_AUTOSENS", true)
break
}
// let it here crash on NPE to get more data as i cannot reproduce this bug
val deviationSlope = (ad.avgDeviation - avgDeviation) / (ad.time - bgTime) * 1000 * 60 * 5
if (ad.avgDeviation > maxDeviation) {
slopeFromMaxDeviation = min(0.0, deviationSlope)
maxDeviation = ad.avgDeviation
}
if (ad.avgDeviation < minDeviation) {
slopeFromMinDeviation = max(0.0, deviationSlope)
minDeviation = ad.avgDeviation
}
past++
// https://github.com/openaps/oref0/blob/master/lib/determine-basal/cob-autosens.js#L169
if (i < bucketedData.size - 16) { // we need 1h of data to calculate minDeviationSlope
@Suppress("UNUSED_VARIABLE") var maxDeviation = 0.0
@Suppress("UNUSED_VARIABLE") var minDeviation = 999.0
val hourAgo = bgTime + 10 * 1000 - 60 * 60 * 1000L
val hourAgoData = ads.getAutosensDataAtTime(hourAgo)
if (hourAgoData != null) {
val initialIndex = autosensDataTable.indexOfKey(hourAgoData.time)
aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketedData.size + " i=" + i + " hourAgoData=" + hourAgoData.toString())
var past = 1
try {
while (past < 12) {
val ad = autosensDataTable.valueAt(initialIndex + past)
aapsLogger.debug(LTag.AUTOSENS, ">>>>> past=" + past + " ad=" + ad?.toString())
if (ad == null) {
aapsLogger.debug(LTag.AUTOSENS, autosensDataTable.toString())
aapsLogger.debug(LTag.AUTOSENS, bucketedData.toString())
//aapsLogger.debug(LTag.AUTOSENS, iobCobCalculatorPlugin.getBgReadingsDataTable().toString())
val notification = Notification(Notification.SEND_LOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW)
rxBus.send(EventNewNotification(notification))
sp.putBoolean("log_AUTOSENS", true)
break
}
} catch (e: Exception) {
aapsLogger.error("Unhandled exception", e)
fabricPrivacy.logException(e)
aapsLogger.debug(autosensDataTable.toString())
aapsLogger.debug(bucketedData.toString())
//aapsLogger.debug(iobCobCalculatorPlugin.getBgReadingsDataTable().toString())
val notification = Notification(Notification.SEND_LOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW)
rxBus.send(EventNewNotification(notification))
sp.putBoolean("log_AUTOSENS", true)
break
}
} else {
aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketedData.size + " i=" + i + " hourAgoData=" + "null")
}
}
val recentCarbTreatments = repository.getCarbsDataFromTimeToTimeExpanded(bgTime - T.mins(5).msecs(), bgTime, true).blockingGet()
for (recentCarbTreatment in recentCarbTreatments) {
autosensData.carbsFromBolus += recentCarbTreatment.amount
val isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled()
autosensData.activeCarbsList.add(autosensData.CarbsInPast(recentCarbTreatment, isAAPSOrWeighted))
autosensData.pastSensitivity += "[" + DecimalFormatter.to0Decimal(recentCarbTreatment.amount) + "g]"
}
// if we are absorbing carbs
if (previous != null && previous.cob > 0) {
// calculate sum of min carb impact from all active treatments
var totalMinCarbsImpact = 0.0
if (sensitivityAAPSPlugin.isEnabled(PluginType.SENSITIVITY) || sensitivityWeightedAveragePlugin.isEnabled(PluginType.SENSITIVITY)) {
//when the impact depends on a max time, sum them up as smaller carb sizes make them smaller
for (ii in autosensData.activeCarbsList.indices) {
val c = autosensData.activeCarbsList[ii]
totalMinCarbsImpact += c.min5minCarbImpact
}
} else {
//Oref sensitivity
totalMinCarbsImpact = sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact)
}
// figure out how many carbs that represents
// but always assume at least 3mg/dL/5m (default) absorption per active treatment
val ci = max(deviation, totalMinCarbsImpact)
if (ci != deviation) autosensData.failoverToMinAbsorbtionRate = true
autosensData.absorbed = ci * profile.getIc(bgTime) / sens
// and add that to the running total carbsAbsorbed
autosensData.cob = max(previous.cob - autosensData.absorbed, 0.0)
autosensData.substractAbosorbedCarbs()
autosensData.usedMinCarbsImpact = totalMinCarbsImpact
}
val isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled()
autosensData.removeOldCarbs(bgTime, isAAPSOrWeighted)
autosensData.cob += autosensData.carbsFromBolus
autosensData.deviation = deviation
autosensData.bgi = bgi
autosensData.delta = delta
autosensData.avgDelta = avgDelta
autosensData.avgDeviation = avgDeviation
autosensData.slopeFromMaxDeviation = slopeFromMaxDeviation
autosensData.slopeFromMinDeviation = slopeFromMinDeviation
// calculate autosens only without COB
if (autosensData.cob <= 0) {
when {
abs(deviation) < Constants.DEVIATION_TO_BE_EQUAL -> {
autosensData.pastSensitivity += "="
autosensData.validDeviation = true
}
deviation > 0 -> {
autosensData.pastSensitivity += "+"
autosensData.validDeviation = true
}
else -> {
autosensData.pastSensitivity += "-"
autosensData.validDeviation = true
// let it here crash on NPE to get more data as i cannot reproduce this bug
val deviationSlope = (ad.avgDeviation - avgDeviation) / (ad.time - bgTime) * 1000 * 60 * 5
if (ad.avgDeviation > maxDeviation) {
slopeFromMaxDeviation = min(0.0, deviationSlope)
maxDeviation = ad.avgDeviation
}
if (ad.avgDeviation < minDeviation) {
slopeFromMinDeviation = max(0.0, deviationSlope)
minDeviation = ad.avgDeviation
}
past++
}
} catch (e: Exception) {
aapsLogger.error("Unhandled exception", e)
fabricPrivacy.logException(e)
aapsLogger.debug(autosensDataTable.toString())
aapsLogger.debug(bucketedData.toString())
//aapsLogger.debug(iobCobCalculatorPlugin.getBgReadingsDataTable().toString())
val notification = Notification(Notification.SEND_LOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW)
rxBus.send(EventNewNotification(notification))
sp.putBoolean("log_AUTOSENS", true)
break
}
} else {
autosensData.pastSensitivity += "C"
aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketedData.size + " i=" + i + " hourAgoData=" + "null")
}
previous = autosensData
if (bgTime < dateUtil.now()) autosensDataTable.put(bgTime, autosensData)
aapsLogger.debug(LTag.AUTOSENS, "Running detectSensitivity from: " + dateUtil.dateAndTimeString(oldestTimeWithData) + " to: " + dateUtil.dateAndTimeString(bgTime) + " lastDataTime:" + iobCobCalculatorPlugin.lastDataTime())
val sensitivity = iobCobCalculatorPlugin.detectSensitivityWithLock(oldestTimeWithData, bgTime)
aapsLogger.debug(LTag.AUTOSENS, "Sensitivity result: $sensitivity")
autosensData.autosensResult = sensitivity
aapsLogger.debug(LTag.AUTOSENS, autosensData.toString())
}
val recentCarbTreatments = repository.getCarbsDataFromTimeToTimeExpanded(bgTime - T.mins(5).msecs(), bgTime, true).blockingGet()
for (recentCarbTreatment in recentCarbTreatments) {
autosensData.carbsFromBolus += recentCarbTreatment.amount
val isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled()
autosensData.activeCarbsList.add(autosensData.CarbsInPast(recentCarbTreatment, isAAPSOrWeighted))
autosensData.pastSensitivity += "[" + DecimalFormatter.to0Decimal(recentCarbTreatment.amount) + "g]"
}
// if we are absorbing carbs
if (previous != null && previous.cob > 0) {
// calculate sum of min carb impact from all active treatments
var totalMinCarbsImpact = 0.0
if (sensitivityAAPSPlugin.isEnabled(PluginType.SENSITIVITY) || sensitivityWeightedAveragePlugin.isEnabled(PluginType.SENSITIVITY)) {
//when the impact depends on a max time, sum them up as smaller carb sizes make them smaller
for (ii in autosensData.activeCarbsList.indices) {
val c = autosensData.activeCarbsList[ii]
totalMinCarbsImpact += c.min5minCarbImpact
}
} else {
//Oref sensitivity
totalMinCarbsImpact = sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact)
}
// figure out how many carbs that represents
// but always assume at least 3mg/dL/5m (default) absorption per active treatment
val ci = max(deviation, totalMinCarbsImpact)
if (ci != deviation) autosensData.failoverToMinAbsorbtionRate = true
autosensData.absorbed = ci * profile.getIc(bgTime) / sens
// and add that to the running total carbsAbsorbed
autosensData.cob = max(previous.cob - autosensData.absorbed, 0.0)
autosensData.substractAbosorbedCarbs()
autosensData.usedMinCarbsImpact = totalMinCarbsImpact
}
val isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled()
autosensData.removeOldCarbs(bgTime, isAAPSOrWeighted)
autosensData.cob += autosensData.carbsFromBolus
autosensData.deviation = deviation
autosensData.bgi = bgi
autosensData.delta = delta
autosensData.avgDelta = avgDelta
autosensData.avgDeviation = avgDeviation
autosensData.slopeFromMaxDeviation = slopeFromMaxDeviation
autosensData.slopeFromMinDeviation = slopeFromMinDeviation
// calculate autosens only without COB
if (autosensData.cob <= 0) {
when {
abs(deviation) < Constants.DEVIATION_TO_BE_EQUAL -> {
autosensData.pastSensitivity += "="
autosensData.validDeviation = true
}
deviation > 0 -> {
autosensData.pastSensitivity += "+"
autosensData.validDeviation = true
}
else -> {
autosensData.pastSensitivity += "-"
autosensData.validDeviation = true
}
}
} else {
autosensData.pastSensitivity += "C"
}
previous = autosensData
if (bgTime < dateUtil.now()) autosensDataTable.put(bgTime, autosensData)
aapsLogger.debug(LTag.AUTOSENS, "Running detectSensitivity from: " + dateUtil.dateAndTimeString(oldestTimeWithData) + " to: " + dateUtil.dateAndTimeString(bgTime) + " lastDataTime:" + ads.lastDataTime(dateUtil))
val sensitivity = activePlugin.activeSensitivity.detectSensitivity(ads, oldestTimeWithData, bgTime)
aapsLogger.debug(LTag.AUTOSENS, "Sensitivity result: $sensitivity")
autosensData.autosensResult = sensitivity
aapsLogger.debug(LTag.AUTOSENS, autosensData.toString())
}
iobCobCalculatorPlugin.ads = ads
Thread {
SystemClock.sleep(1000)
rxBus.send(EventAutosensCalculationFinished(cause))
@ -267,7 +274,6 @@ class IobCobThread @Inject internal constructor(
mWakeLock?.release()
rxBus.send(EventIobCalculationProgress(""))
aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: $from")
aapsLogger.debug(LTag.AUTOSENS, "Midnights: " + MidnightTime.log())
profiler.log(LTag.AUTOSENS, "IobCobThread", start)
}
}

View file

@ -2,12 +2,12 @@ package info.nightscout.androidaps.plugins.sensitivity
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.SensitivityInterface
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensDataStore
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult
import info.nightscout.androidaps.utils.Round
import info.nightscout.androidaps.utils.SafeParse
@ -24,7 +24,7 @@ abstract class AbstractSensitivityPlugin(
val sp: SP
) : PluginBase(pluginDescription, aapsLogger, resourceHelper, injector), SensitivityInterface {
abstract override fun detectSensitivity(plugin: IobCobCalculator, fromTime: Long, toTime: Long): AutosensResult
abstract override fun detectSensitivity(ads: AutosensDataStore, fromTime: Long, toTime: Long): AutosensResult
fun fillResult(ratio: Double, carbsAbsorbed: Double, pastSensitivity: String,
ratioLimit: String, sensResult: String, deviationsArraySize: Int): AutosensResult {

View file

@ -9,13 +9,13 @@ import info.nightscout.androidaps.database.entities.TherapyEvent
import info.nightscout.androidaps.db.ProfileSwitch
import info.nightscout.androidaps.extensions.isEvent5minBack
import info.nightscout.androidaps.interfaces.DatabaseHelperInterface
import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.interfaces.SensitivityInterface.SensitivityType
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensDataStore
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
import info.nightscout.androidaps.utils.DateUtil
@ -48,8 +48,7 @@ open class SensitivityAAPSPlugin @Inject constructor(
injector, aapsLogger, resourceHelper, sp
) {
override fun detectSensitivity(plugin: IobCobCalculator, fromTime: Long, toTime: Long): AutosensResult {
val autosensDataTable = plugin.getAutosensDataTable()
override fun detectSensitivity(ads: AutosensDataStore, fromTime: Long, toTime: Long): AutosensResult {
val age = sp.getString(R.string.key_age, "")
var defaultHours = 24
if (age == resourceHelper.gs(R.string.key_adult)) defaultHours = 24
@ -61,13 +60,13 @@ open class SensitivityAAPSPlugin @Inject constructor(
aapsLogger.error("No profile")
return AutosensResult()
}
if (autosensDataTable.size() < 4) {
aapsLogger.debug(LTag.AUTOSENS, "No autosens data available. lastDataTime=" + plugin.lastDataTime())
if (ads.autosensDataTable.size() < 4) {
aapsLogger.debug(LTag.AUTOSENS, "No autosens data available. lastDataTime=" + ads.lastDataTime(dateUtil))
return AutosensResult()
}
val current = plugin.getAutosensData(toTime) // this is running inside lock already
val current = ads.getAutosensDataAtTime(toTime) // this is running inside lock already
if (current == null) {
aapsLogger.debug(LTag.AUTOSENS, "No autosens data available. toTime: " + dateUtil.dateAndTimeString(toTime) + " lastDataTime: " + plugin.lastDataTime())
aapsLogger.debug(LTag.AUTOSENS, "No autosens data available. toTime: " + dateUtil.dateAndTimeString(toTime) + " lastDataTime: " + ads.lastDataTime(dateUtil))
return AutosensResult()
}
val siteChanges = repository.getTherapyEventDataFromTime(fromTime, TherapyEvent.Type.CANNULA_CHANGE, true).blockingGet()
@ -75,8 +74,8 @@ open class SensitivityAAPSPlugin @Inject constructor(
val deviationsArray: MutableList<Double> = ArrayList()
var pastSensitivity = ""
var index = 0
while (index < autosensDataTable.size()) {
val autosensData = autosensDataTable.valueAt(index)
while (index < ads.autosensDataTable.size()) {
val autosensData = ads.autosensDataTable.valueAt(index)
if (autosensData.time < fromTime) {
index++
continue

View file

@ -9,7 +9,6 @@ import info.nightscout.androidaps.database.entities.TherapyEvent
import info.nightscout.androidaps.db.ProfileSwitch
import info.nightscout.androidaps.extensions.isEvent5minBack
import info.nightscout.androidaps.interfaces.DatabaseHelperInterface
import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.androidaps.interfaces.ProfileFunction
@ -17,6 +16,7 @@ import info.nightscout.androidaps.interfaces.SensitivityInterface.SensitivityTyp
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensDataStore
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
import info.nightscout.androidaps.utils.DateUtil
@ -51,24 +51,23 @@ open class SensitivityOref1Plugin @Inject constructor(
injector, aapsLogger, resourceHelper, sp
) {
override fun detectSensitivity(plugin: IobCobCalculator, fromTime: Long, toTime: Long): AutosensResult {
override fun detectSensitivity(ads: AutosensDataStore, fromTime: Long, toTime: Long): AutosensResult {
// todo this method is called from the IobCobCalculatorPlugin, which leads to a circular
// dependency, this should be avoided
val autosensDataTable = plugin.getAutosensDataTable()
val profile = profileFunction.getProfile()
if (profile == null) {
aapsLogger.error("No profile")
return AutosensResult()
}
if (autosensDataTable.size() < 4) {
aapsLogger.debug(LTag.AUTOSENS, "No autosens data available. lastDataTime=" + plugin.lastDataTime())
if (ads.autosensDataTable.size() < 4) {
aapsLogger.debug(LTag.AUTOSENS, "No autosens data available. lastDataTime=" + ads.lastDataTime(dateUtil))
return AutosensResult()
}
// the current
val current = plugin.getAutosensData(toTime) // this is running inside lock already
val current = ads.getAutosensDataAtTime(toTime) // this is running inside lock already
if (current == null) {
aapsLogger.debug(LTag.AUTOSENS, "No autosens data available. toTime: " + dateUtil.dateAndTimeString(toTime) + " lastDataTime: " + plugin.lastDataTime())
aapsLogger.debug(LTag.AUTOSENS, "No autosens data available. toTime: " + dateUtil.dateAndTimeString(toTime) + " lastDataTime: " + ads.lastDataTime(dateUtil))
return AutosensResult()
}
val siteChanges = repository.getTherapyEventDataFromTime(fromTime, TherapyEvent.Type.CANNULA_CHANGE, true).blockingGet()
@ -85,8 +84,8 @@ open class SensitivityOref1Plugin @Inject constructor(
val ratioLimitArray = mutableListOf("", "")
val hoursDetection = listOf(8.0, 24.0)
var index = 0
while (index < autosensDataTable.size()) {
val autosensData = autosensDataTable.valueAt(index)
while (index < ads.autosensDataTable.size()) {
val autosensData = ads.autosensDataTable.valueAt(index)
if (autosensData.time < fromTime) {
index++
continue

View file

@ -8,17 +8,17 @@ import info.nightscout.androidaps.data.Profile
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.TherapyEvent
import info.nightscout.androidaps.db.ProfileSwitch
import info.nightscout.androidaps.extensions.isEvent5minBack
import info.nightscout.androidaps.interfaces.DatabaseHelperInterface
import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.interfaces.SensitivityInterface.SensitivityType
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensDataStore
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.extensions.isEvent5minBack
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
import org.json.JSONException
@ -47,21 +47,20 @@ open class SensitivityWeightedAveragePlugin @Inject constructor(
injector, aapsLogger, resourceHelper, sp
) {
override fun detectSensitivity(plugin: IobCobCalculator, fromTime: Long, toTime: Long): AutosensResult {
val autosensDataTable = plugin.getAutosensDataTable()
override fun detectSensitivity(ads: AutosensDataStore, fromTime: Long, toTime: Long): AutosensResult {
val age = sp.getString(R.string.key_age, "")
var defaultHours = 24
if (age == resourceHelper.gs(R.string.key_adult)) defaultHours = 24
if (age == resourceHelper.gs(R.string.key_teenage)) defaultHours = 4
if (age == resourceHelper.gs(R.string.key_child)) defaultHours = 4
val hoursForDetection = sp.getInt(R.string.key_openapsama_autosens_period, defaultHours)
if (autosensDataTable.size() < 4) {
aapsLogger.debug(LTag.AUTOSENS, "No autosens data available. lastDataTime=" + plugin.lastDataTime())
if (ads.autosensDataTable.size() < 4) {
aapsLogger.debug(LTag.AUTOSENS, "No autosens data available. lastDataTime=" + ads.lastDataTime(dateUtil))
return AutosensResult()
}
val current = plugin.getAutosensData(toTime) // this is running inside lock already
val current = ads.getAutosensDataAtTime(toTime) // this is running inside lock already
if (current == null) {
aapsLogger.debug(LTag.AUTOSENS, "No autosens data available. toTime: " + dateUtil.dateAndTimeString(toTime) + " lastDataTime: " + plugin.lastDataTime())
aapsLogger.debug(LTag.AUTOSENS, "No autosens data available. toTime: " + dateUtil.dateAndTimeString(toTime) + " lastDataTime: " + ads.lastDataTime(dateUtil))
return AutosensResult()
}
val profile = profileFunction.getProfile()
@ -74,8 +73,8 @@ open class SensitivityWeightedAveragePlugin @Inject constructor(
var pastSensitivity = ""
var index = 0
val data = LongSparseArray<Double>()
while (index < autosensDataTable.size()) {
val autosensData = autosensDataTable.valueAt(index)
while (index < ads.autosensDataTable.size()) {
val autosensData = ads.autosensDataTable.valueAt(index)
if (autosensData.time < fromTime) {
index++
continue

View file

@ -102,7 +102,7 @@ class KeepAliveReceiver : DaggerBroadcastReceiver() {
var shouldUploadStatus = false
if (config.NSCLIENT) return
if (config.PUMPCONTROL) shouldUploadStatus = true
else if (!loopPlugin.isEnabled() || iobCobCalculator.actualBg() == null)
else if (!loopPlugin.isEnabled() || iobCobCalculator.ads.actualBg() == null)
shouldUploadStatus = true
else if (dateUtil.isOlderThan(activePlugin.activeAPS.lastAPSRun, 5)) shouldUploadStatus = true
if (dateUtil.isOlderThan(lastIobUpload, IOB_UPDATE_FREQUENCY_IN_MINUTES) && shouldUploadStatus) {

View file

@ -110,7 +110,7 @@ class LocalAlertUtils @Inject constructor(
}
fun checkStaleBGAlert() {
val bgReading = iobCobCalculator.lastBg()
val bgReading = iobCobCalculator.ads.lastBg()
if (sp.getBoolean(R.string.key_enable_missed_bg_readings_alert, false)
&& bgReading != null && bgReading.timestamp + missedReadingsThreshold() < System.currentTimeMillis() && sp.getLong("nextMissedReadingsAlarm", 0L) < System.currentTimeMillis()) {
val n = Notification(Notification.BG_READINGS_MISSED, resourceHelper.gs(R.string.missed_bg_readings), Notification.URGENT)

View file

@ -299,7 +299,7 @@ class BolusWizard @Inject constructor(
}
if (insulinFromCOB > 0) {
actions.add(resourceHelper.gs(R.string.cobvsiob) + ": " + resourceHelper.gs(R.string.formatsignedinsulinunits, insulinFromBolusIOB + insulinFromBasalIOB + insulinFromCOB + insulinFromBG).formatColor(resourceHelper, R.color.cobAlert))
val absorptionRate = iobCobCalculator.slowAbsorptionPercentage(60)
val absorptionRate = iobCobCalculator.ads.slowAbsorptionPercentage(60)
if (absorptionRate > .25)
actions.add(resourceHelper.gs(R.string.slowabsorptiondetected, resourceHelper.gc(R.color.cobAlert), (absorptionRate * 100).toInt()))
}

View file

@ -24,6 +24,7 @@ import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.general.smsCommunicator.otp.OneTimePassword
import info.nightscout.androidaps.plugins.general.smsCommunicator.otp.OneTimePasswordValidationResult
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensDataStore
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.CobInfo
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
@ -59,7 +60,7 @@ import java.util.*
ConstraintChecker::class, FabricPrivacy::class, VirtualPumpPlugin::class, XdripCalibrations::class,
SmsManager::class, CommandQueue::class, LocalProfilePlugin::class, DateUtil::class,
OneTimePassword::class, UserEntryLogger::class, LoopPlugin::class,
AppRepository::class, DateUtil::class)
AppRepository::class, DateUtil::class, AutosensDataStore::class)
class SmsCommunicatorPluginTest : TestBaseWithProfile() {
@Mock lateinit var sp: SP
@ -75,6 +76,7 @@ class SmsCommunicatorPluginTest : TestBaseWithProfile() {
@Mock lateinit var uel: UserEntryLogger
@Mock lateinit var repository: AppRepository
@Mock lateinit var dateUtilMocked: DateUtil
@Mock lateinit var autosensDataStore: AutosensDataStore
var injector: HasAndroidInjector = HasAndroidInjector {
AndroidInjector {
@ -100,7 +102,8 @@ class SmsCommunicatorPluginTest : TestBaseWithProfile() {
bgList.add(reading)
`when`(iobCobCalculator.getCobInfo(false, "SMS COB")).thenReturn(CobInfo(10.0, 2.0))
`when`(iobCobCalculator.lastBg()).thenReturn(reading)
`when`(iobCobCalculator.ads).thenReturn(autosensDataStore)
`when`(autosensDataStore.lastBg()).thenReturn(reading)
PowerMockito.mockStatic(SmsManager::class.java)
val smsManager = PowerMockito.mock(SmsManager::class.java)

View file

@ -3,10 +3,10 @@ package info.nightscout.androidaps.plugins.sensitivity
import dagger.android.AndroidInjector
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.TestBase
import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.SensitivityInterface
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensDataStore
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
@ -26,7 +26,7 @@ class AbstractSensitivityPluginTest : TestBase() {
private inner class SensitivityTestClass(pluginDescription: PluginDescription, aapsLogger: AAPSLogger, resourceHelper: ResourceHelper, sp: SP) : AbstractSensitivityPlugin(pluginDescription, HasAndroidInjector { AndroidInjector { } }, aapsLogger, resourceHelper, sp) {
override fun detectSensitivity(plugin: IobCobCalculator, fromTime: Long, toTime: Long): AutosensResult {
override fun detectSensitivity(ads: AutosensDataStore, fromTime: Long, toTime: Long): AutosensResult {
return AutosensResult()
}
@ -35,7 +35,7 @@ class AbstractSensitivityPluginTest : TestBase() {
override fun configuration(): JSONObject = JSONObject()
override fun applyConfiguration(configuration: JSONObject) { }
override fun applyConfiguration(configuration: JSONObject) {}
}
@Test

View file

@ -11,6 +11,7 @@ import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensDataStore
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
@ -27,7 +28,7 @@ import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
@RunWith(PowerMockRunner::class)
@PrepareForTest(ConstraintChecker::class, VirtualPumpPlugin::class, DateUtil::class)
@PrepareForTest(ConstraintChecker::class, VirtualPumpPlugin::class, DateUtil::class, AutosensDataStore::class)
class BolusWizardTest : TestBase() {
private val pumpBolusStep = 0.1
@ -43,6 +44,7 @@ class BolusWizardTest : TestBase() {
@Mock lateinit var treatmentsPlugin: TreatmentsPlugin
@Mock lateinit var virtualPumpPlugin: VirtualPumpPlugin
@Mock lateinit var dateUtil: DateUtil
@Mock lateinit var autosensDataStore: AutosensDataStore
val injector = HasAndroidInjector {
AndroidInjector {
@ -77,6 +79,7 @@ class BolusWizardTest : TestBase() {
val pumpDescription = PumpDescription()
pumpDescription.bolusStep = pumpBolusStep
`when`(virtualPumpPlugin.pumpDescription).thenReturn(pumpDescription)
`when`(iobCobCalculator.ads).thenReturn(autosensDataStore)
Mockito.doAnswer { invocation: InvocationOnMock ->
invocation.getArgument<Constraint<Double>>(0)

View file

@ -30,7 +30,7 @@ class TriggerAutosensValue(injector: HasAndroidInjector) : Trigger(injector) {
}
override fun shouldRun(): Boolean {
val autosensData = iobCobCalculator.getLastAutosensData("Automation trigger")
val autosensData = iobCobCalculator.ads.getLastAutosensData("Automation trigger", aapsLogger, dateUtil)
?: return if (comparator.value == Comparator.Compare.IS_NOT_AVAILABLE) {
aapsLogger.debug(LTag.AUTOMATION, "Ready for execution: " + friendlyDescription())
true

View file

@ -34,7 +34,7 @@ class TriggerIob(injector: HasAndroidInjector) : Trigger(injector) {
override fun shouldRun(): Boolean {
val profile = profileFunction.getProfile() ?: return false
val iob = iobCobCalculator.calculateFromTreatmentsAndTempsSynchronized(dateUtil.now(), profile)
val iob = iobCobCalculator.calculateFromTreatmentsAndTemps(dateUtil.now(), profile)
if (comparator.value.check(iob.iob, insulin.value)) {
aapsLogger.debug(LTag.AUTOMATION, "Ready for execution: " + friendlyDescription())
return true

View file

@ -25,7 +25,7 @@ class TriggerAutosensValueTest : TriggerTestBase() {
@Test fun shouldRunTest() {
`when`(sp.getDouble(Mockito.eq(R.string.key_openapsama_autosens_max), ArgumentMatchers.anyDouble())).thenReturn(1.2)
`when`(sp.getDouble(Mockito.eq(R.string.key_openapsama_autosens_min), ArgumentMatchers.anyDouble())).thenReturn(0.7)
`when`(iobCobCalculator.getLastAutosensData("Automation trigger")).thenReturn(generateAutosensData())
`when`(autosensDataStore.getLastAutosensData(anyObject(), anyObject(), anyObject())).thenReturn(generateAutosensData())
var t = TriggerAutosensValue(injector)
t.autosens.value = 110.0
t.comparator.value = Comparator.Compare.IS_EQUAL
@ -65,14 +65,14 @@ class TriggerAutosensValueTest : TriggerTestBase() {
t.autosens.value = 390.0
t.comparator.value = Comparator.Compare.IS_EQUAL_OR_LESSER
Assert.assertTrue(t.shouldRun())
PowerMockito.`when`(iobCobCalculator.getLastAutosensData("Automation trigger")).thenReturn(AutosensData(injector))
PowerMockito.`when`(autosensDataStore.getLastAutosensData(anyObject(), anyObject(), anyObject())).thenReturn(AutosensData(injector))
t = TriggerAutosensValue(injector)
t.autosens.value = 80.0
t.comparator.value = Comparator.Compare.IS_EQUAL_OR_LESSER
Assert.assertFalse(t.shouldRun())
// Test autosensData == null and Comparator == IS_NOT_AVAILABLE
PowerMockito.`when`(iobCobCalculator.getLastAutosensData("Automation trigger")).thenReturn(null)
PowerMockito.`when`(autosensDataStore.getLastAutosensData(anyObject(), anyObject(), anyObject())).thenReturn(null)
t = TriggerAutosensValue(injector)
t.comparator.value = Comparator.Compare.IS_NOT_AVAILABLE
Assert.assertTrue(t.shouldRun())
@ -109,7 +109,7 @@ class TriggerAutosensValueTest : TriggerTestBase() {
}
@Test fun iconTest() {
Assert.assertEquals(Optional.of(R.drawable.`ic_as`), TriggerAutosensValue(injector).icon())
Assert.assertEquals(Optional.of(R.drawable.ic_as), TriggerAutosensValue(injector).icon())
}
@Before

View file

@ -31,7 +31,7 @@ class TriggerBgTest : TriggerTestBase() {
@Test
fun shouldRunTest() {
`when`(iobCobCalculator.getBgReadingsDataTableCopy()).thenReturn(generateOneCurrentRecordBgData())
`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateOneCurrentRecordBgData())
var t: TriggerBg = TriggerBg(injector).setUnits(Constants.MMOL).setValue(4.1).comparator(Comparator.Compare.IS_EQUAL)
Assert.assertFalse(t.shouldRun())
t = TriggerBg(injector).setUnits(Constants.MGDL).setValue(214.0).comparator(Comparator.Compare.IS_EQUAL)
@ -50,7 +50,7 @@ class TriggerBgTest : TriggerTestBase() {
Assert.assertTrue(t.shouldRun())
t = TriggerBg(injector).setUnits(Constants.MGDL).setValue(213.0).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER)
Assert.assertFalse(t.shouldRun())
`when`(iobCobCalculator.getBgReadingsDataTableCopy()).thenReturn(ArrayList())
`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(ArrayList())
t = TriggerBg(injector).setUnits(Constants.MGDL).setValue(213.0).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER)
Assert.assertFalse(t.shouldRun())
t = TriggerBg(injector).comparator(Comparator.Compare.IS_NOT_AVAILABLE)

View file

@ -32,7 +32,7 @@ class TriggerDeltaTest : TriggerTestBase() {
}
@Test fun shouldRunTest() {
`when`(iobCobCalculator.getBgReadingsDataTableCopy()).thenReturn(generateValidBgData())
`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateValidBgData())
var t = TriggerDelta(injector).units(Constants.MGDL).setValue(73.0, DeltaType.LONG_AVERAGE).comparator(Comparator.Compare.IS_EQUAL)
Assert.assertFalse(t.shouldRun())
Assert.assertEquals(DeltaType.LONG_AVERAGE, t.delta.deltaType)
@ -54,7 +54,7 @@ class TriggerDeltaTest : TriggerTestBase() {
Assert.assertFalse(t.shouldRun())
t = TriggerDelta(injector).units(Constants.MGDL).setValue(-0.2, DeltaType.LONG_AVERAGE).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER)
Assert.assertTrue(t.shouldRun())
`when`(iobCobCalculator.getBgReadingsDataTableCopy()).thenReturn(ArrayList())
`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(ArrayList())
t = TriggerDelta(injector).units(Constants.MGDL).setValue(213.0, DeltaType.DELTA).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER)
Assert.assertFalse(t.shouldRun())
t = TriggerDelta(injector).comparator(Comparator.Compare.IS_NOT_AVAILABLE)

View file

@ -27,7 +27,7 @@ class TriggerIobTest : TriggerTestBase() {
}
@Test fun shouldRunTest() {
`when`(iobCobCalculator.calculateFromTreatmentsAndTempsSynchronized(ArgumentMatchers.anyLong(), anyObject())).thenReturn(generateIobRecordData())
`when`(iobCobCalculator.calculateFromTreatmentsAndTemps(ArgumentMatchers.anyLong(), anyObject())).thenReturn(generateIobRecordData())
var t: TriggerIob = TriggerIob(injector).setValue(1.1).comparator(Comparator.Compare.IS_EQUAL)
Assert.assertFalse(t.shouldRun())
t = TriggerIob(injector).setValue(1.0).comparator(Comparator.Compare.IS_EQUAL)

View file

@ -10,6 +10,7 @@ import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensDataStore
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.androidaps.receivers.ReceiverStatusStore
import info.nightscout.androidaps.services.LastLocationDataContainer
@ -19,13 +20,14 @@ import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.powermock.core.classloader.annotations.PrepareForTest
@PrepareForTest(LastLocationDataContainer::class, AutomationPlugin::class)
@PrepareForTest(LastLocationDataContainer::class, AutomationPlugin::class, AutosensDataStore::class)
open class TriggerTestBase : TestBaseWithProfile() {
@Mock lateinit var sp: SP
@Mock lateinit var locationDataContainer: LastLocationDataContainer
@Mock lateinit var activePlugin: ActivePluginProvider
@Mock lateinit var iobCobCalculator: IobCobCalculator
@Mock lateinit var autosensDataStore: AutosensDataStore
@Mock lateinit var context: Context
@Mock lateinit var automationPlugin: AutomationPlugin
@ -38,6 +40,7 @@ open class TriggerTestBase : TestBaseWithProfile() {
receiverStatusStore = ReceiverStatusStore(context, rxBus)
testPumpPlugin = TestPumpPlugin(pluginDescription, aapsLogger, resourceHelper, injector)
`when`(activePlugin.activePump).thenReturn(testPumpPlugin)
`when`(iobCobCalculator.ads).thenReturn(autosensDataStore)
}
var injector: HasAndroidInjector = HasAndroidInjector {

View file

@ -1,13 +1,11 @@
package info.nightscout.androidaps.interfaces
import androidx.collection.LongSparseArray
import info.nightscout.androidaps.data.InMemoryGlucoseValue
import info.nightscout.androidaps.data.IobTotal
import info.nightscout.androidaps.data.MealData
import info.nightscout.androidaps.data.Profile
import info.nightscout.androidaps.database.entities.ExtendedBolus
import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.database.entities.TemporaryBasal
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensDataStore
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.BasalData
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.CobInfo
@ -16,36 +14,21 @@ import org.json.JSONArray
interface IobCobCalculator {
val dataLock: Any
fun getBgReadingsDataTableCopy(): List<GlucoseValue>
fun getBucketedDataTableCopy(): MutableList<InMemoryGlucoseValue>?
fun getAutosensDataTable(): LongSparseArray<AutosensData>
var ads: AutosensDataStore
fun getMealDataWithWaitingForCalculationFinish(): MealData
fun lastDataTime(): String
fun getAutosensData(fromTime: Long): AutosensData?
fun getLastAutosensData(reason: String): AutosensData?
fun getLastAutosensDataWithWaitForCalculationFinish(reason: String): AutosensData?
fun calculateIobArrayInDia(profile: Profile): Array<IobTotal>
fun calculateAbsInsulinFromTreatmentsAndTempsSynchronized(fromTime: Long): IobTotal
fun calculateFromTreatmentsAndTempsSynchronized(time: Long, profile: Profile): IobTotal
fun calculateAbsInsulinFromTreatmentsAndTemps(fromTime: Long): IobTotal
fun calculateFromTreatmentsAndTemps(time: Long, profile: Profile): IobTotal
fun getBasalData(profile: Profile, fromTime: Long): BasalData
fun calculateIobArrayInDia(profile: Profile): Array<IobTotal>
fun calculateIobArrayForSMB(lastAutosensResult: AutosensResult, exercise_mode: Boolean, half_basal_exercise_target: Int, isTempTarget: Boolean): Array<IobTotal>
fun iobArrayToString(array: Array<IobTotal>): String
fun slowAbsorptionPercentage(timeInMinutes: Int): Double
fun convertToJSONArray(iobArray: Array<IobTotal>): JSONArray
/**
* Return last valid (>39) GlucoseValue from database or null if db is empty
*
* @return GlucoseValue or null
*/
fun lastBg(): GlucoseValue?
/**
* Calculate CobInfo to now()
*
@ -55,13 +38,6 @@ interface IobCobCalculator {
*/
fun getCobInfo(waitForCalculationFinish: Boolean, reason: String): CobInfo
/**
* Provide last GlucoseValue or null if none exists withing last 9 minutes
*
* @return GlucoseValue or null
*/
fun actualBg(): GlucoseValue?
/**
* Calculate IobTotal from boluses and extended boluses to now().
* NOTE: Only isValid == true boluses are included

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.interfaces
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensDataStore
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult
interface SensitivityInterface : ConfigExportImportInterface {
@ -11,15 +12,17 @@ interface SensitivityInterface : ConfigExportImportInterface {
SENSITIVITY_OREF1(2);
companion object {
private val map = values().associateBy(SensitivityType::value)
fun fromInt(type: Int) = map[type]
}
}
val id: SensitivityType
fun detectSensitivity(plugin: IobCobCalculator, fromTime: Long, toTime: Long): AutosensResult
fun detectSensitivity(ads: AutosensDataStore, fromTime: Long, toTime: Long): AutosensResult
companion object {
const val MIN_HOURS = 1.0
const val MIN_HOURS_FULL_AUTOSENS = 4.0
}

View file

@ -0,0 +1,350 @@
package info.nightscout.androidaps.plugins.iob.iobCobCalculator
import androidx.collection.LongSparseArray
import info.nightscout.androidaps.data.InMemoryGlucoseValue
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.T
import kotlin.math.abs
import kotlin.math.roundToLong
class AutosensDataStore {
private val dataLock = Any()
private var lastUsed5minCalculation: Boolean? = null // true if used 5min bucketed data
// 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
var bgReadings: List<GlucoseValue> = listOf() // newest at index 0
@Synchronized set
@Synchronized get
var autosensDataTable = LongSparseArray<AutosensData>() // oldest at index 0
@Synchronized set
@Synchronized get
var bucketedData: MutableList<InMemoryGlucoseValue>? = null
@Synchronized set
@Synchronized get
fun clone(): AutosensDataStore =
AutosensDataStore().also {
synchronized(dataLock) {
it.bgReadings = this.bgReadings.toMutableList()
it.autosensDataTable = this.autosensDataTable.clone()
it.bucketedData = this.bucketedData?.toMutableList()
}
}
fun getBucketedDataTableCopy(): MutableList<InMemoryGlucoseValue>? = synchronized(dataLock) { bucketedData?.toMutableList() }
fun getBgReadingsDataTableCopy(): List<GlucoseValue> = synchronized(dataLock) { bgReadings.toMutableList() }
fun reset() {
synchronized(autosensDataTable) { autosensDataTable = LongSparseArray() }
}
fun newHistoryData(time: Long, aapsLogger: AAPSLogger, dateUtil: DateUtil) {
synchronized(autosensDataTable) {
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
}
}
}
}
// roundup to whole minute
fun roundUpTime(time: Long): Long {
return if (time % 60000 == 0L) time else (time / 60000 + 1) * 60000
}
/**
* Return last valid (>39) GlucoseValue from database or null if db is empty
*
* @return GlucoseValue or null
*/
fun lastBg(): GlucoseValue? =
synchronized(dataLock) {
if (bgReadings.isNotEmpty()) bgReadings[0]
else null
}
/**
* Provide last GlucoseValue or null if none exists withing last 9 minutes
*
* @return GlucoseValue or null
*/
fun actualBg(): GlucoseValue? {
val lastBg = lastBg() ?: return null
return if (lastBg.timestamp > System.currentTimeMillis() - T.mins(9).msecs()) lastBg else null
}
fun lastDataTime(dateUtil: DateUtil): String =
synchronized(dataLock) {
if (autosensDataTable.size() > 0) dateUtil.dateAndTimeAndSecondsString(autosensDataTable.valueAt(autosensDataTable.size() - 1).time)
else "autosensDataTable empty"
}
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 getAutosensDataAtTime(fromTime: Long): AutosensData? {
synchronized(dataLock) {
val now = System.currentTimeMillis()
if (fromTime > now) return null
val previous = findPreviousTimeFromBucketedData(fromTime) ?: return null
return autosensDataTable[roundUpTime(previous)]
}
}
fun getLastAutosensData(reason: String, aapsLogger: AAPSLogger, dateUtil: DateUtil): AutosensData? {
synchronized(dataLock) {
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
}
}
}
private 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, repository: AppRepository, aapsLogger: AAPSLogger, dateUtil: DateUtil) {
synchronized(dataLock) {
val start = to - T.hours((24 + 10 /* max dia */).toLong()).msecs()
// there can be some readings with time in close future (caused by wrong time setting on sensor)
// so add 2 minutes
bgReadings = repository
.compatGetBgReadingsDataFromTime(start, to + T.mins(2).msecs(), false)
.blockingGet()
.filter { it.value >= 39 }
aapsLogger.debug(LTag.AUTOSENS, "BG data loaded. Size: " + bgReadings.size + " Start date: " + dateUtil.dateAndTimeString(start) + " End date: " + dateUtil.dateAndTimeString(to))
createBucketedData(aapsLogger, dateUtil)
}
}
fun isAbout5minData(aapsLogger: AAPSLogger): Boolean {
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
}
}
fun createBucketedData(aapsLogger: AAPSLogger, dateUtil: DateUtil) {
val fiveMinData = isAbout5minData(aapsLogger)
if (lastUsed5minCalculation != null && lastUsed5minCalculation != fiveMinData) {
// changing mode => clear cache
aapsLogger.debug("Invalidating cached data because of changed mode.")
reset()
}
lastUsed5minCalculation = fiveMinData
if (fiveMinData) createBucketedData5min(aapsLogger, dateUtil) else createBucketedDataRecalculated(aapsLogger, dateUtil)
}
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(aapsLogger: AAPSLogger, dateUtil: DateUtil) {
if (bgReadings.size < 3) {
bucketedData = null
return
}
val newBucketedData = ArrayList<InMemoryGlucoseValue>()
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
newBucketedData.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)
newBucketedData.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()
}
bucketedData = newBucketedData
}
private fun createBucketedData5min(aapsLogger: AAPSLogger, dateUtil: DateUtil) {
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);
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(aapsLogger, dateUtil)
return
}
current.timestamp = previous.timestamp + T.mins(5).msecs()
}
aapsLogger.debug(LTag.AUTOSENS, "Bucketed data created. Size: " + bData.size)
bucketedData = bData
}
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 if (count != 0) sum / count else 0.0
}
}

View file

@ -20,7 +20,7 @@ class GlucoseStatusProvider @Inject constructor(
get() = getGlucoseStatusData()
fun getGlucoseStatusData(allowOldData: Boolean = false): GlucoseStatus? {
val data = iobCobCalculator.getBgReadingsDataTableCopy()
val data = iobCobCalculator.ads.getBgReadingsDataTableCopy()
val sizeRecords = data.size
if (sizeRecords == 0) {
aapsLogger.debug(LTag.GLUCOSE, "sizeRecords==0")

View file

@ -3,6 +3,7 @@ package info.nightscout.androidaps.plugins.iob.iobCalculator
import info.nightscout.androidaps.TestBase
import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensDataStore
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.asRounded
@ -13,6 +14,7 @@ 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.api.mockito.PowerMockito
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
@ -23,11 +25,17 @@ import java.util.*
*/
@Suppress("SpellCheckingInspection")
@RunWith(PowerMockRunner::class)
@PrepareForTest(DateUtil::class)
@PrepareForTest(DateUtil::class, AutosensDataStore::class)
class GlucoseStatusTest : TestBase() {
@Mock lateinit var dateUtil: DateUtil
@Mock lateinit var iobCobCalculatorPlugin: IobCobCalculator
@Mock lateinit var autosensDataStore: AutosensDataStore
@Before
fun prepare() {
`when`(iobCobCalculatorPlugin.ads).thenReturn(autosensDataStore)
}
@Test fun toStringShouldBeOverloaded() {
val glucoseStatus = GlucoseStatus(glucose = 0.0, noise = 0.0, delta = 0.0, shortAvgDelta = 0.0, longAvgDelta = 0.0, date = 0)
@ -40,7 +48,7 @@ class GlucoseStatusTest : TestBase() {
}
@Test fun calculateValidGlucoseStatus() {
PowerMockito.`when`(iobCobCalculatorPlugin.getBgReadingsDataTableCopy()).thenReturn(generateValidBgData())
PowerMockito.`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateValidBgData())
val glucoseStatus = GlucoseStatusProvider(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData!!
Assert.assertEquals(214.0, glucoseStatus.glucose, 0.001)
Assert.assertEquals(-2.0, glucoseStatus.delta, 0.001)
@ -50,7 +58,7 @@ class GlucoseStatusTest : TestBase() {
}
@Test fun calculateMostRecentGlucoseStatus() {
PowerMockito.`when`(iobCobCalculatorPlugin.getBgReadingsDataTableCopy()).thenReturn(generateMostRecentBgData())
PowerMockito.`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateMostRecentBgData())
val glucoseStatus: GlucoseStatus = GlucoseStatusProvider(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData!!
Assert.assertEquals(215.0, glucoseStatus.glucose, 0.001) // (214+216) / 2
Assert.assertEquals(-1.0, glucoseStatus.delta, 0.001)
@ -60,7 +68,7 @@ class GlucoseStatusTest : TestBase() {
}
@Test fun oneRecordShouldProduceZeroDeltas() {
PowerMockito.`when`(iobCobCalculatorPlugin.getBgReadingsDataTableCopy()).thenReturn(generateOneCurrentRecordBgData())
PowerMockito.`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateOneCurrentRecordBgData())
val glucoseStatus: GlucoseStatus = GlucoseStatusProvider(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData!!
Assert.assertEquals(214.0, glucoseStatus.glucose, 0.001)
Assert.assertEquals(0.0, glucoseStatus.delta, 0.001)
@ -70,19 +78,19 @@ class GlucoseStatusTest : TestBase() {
}
@Test fun insufficientDataShouldReturnNull() {
PowerMockito.`when`(iobCobCalculatorPlugin.getBgReadingsDataTableCopy()).thenReturn(generateInsufficientBgData())
PowerMockito.`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateInsufficientBgData())
val glucoseStatus: GlucoseStatus? = GlucoseStatusProvider(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData
Assert.assertEquals(null, glucoseStatus)
}
@Test fun oldDataShouldReturnNull() {
PowerMockito.`when`(iobCobCalculatorPlugin.getBgReadingsDataTableCopy()).thenReturn(generateOldBgData())
PowerMockito.`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateOldBgData())
val glucoseStatus: GlucoseStatus? = GlucoseStatusProvider(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData
Assert.assertEquals(null, glucoseStatus)
}
@Test fun returnOldDataIfAllowed() {
PowerMockito.`when`(iobCobCalculatorPlugin.getBgReadingsDataTableCopy()).thenReturn(generateOldBgData())
PowerMockito.`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateOldBgData())
val glucoseStatus: GlucoseStatus? = GlucoseStatusProvider(aapsLogger, iobCobCalculatorPlugin, dateUtil).getGlucoseStatusData(true)
Assert.assertNotEquals(null, glucoseStatus)
}
@ -92,7 +100,7 @@ class GlucoseStatusTest : TestBase() {
}
@Test fun calculateGlucoseStatusForLibreTestBgData() {
PowerMockito.`when`(iobCobCalculatorPlugin.getBgReadingsDataTableCopy()).thenReturn(generateLibreTestData())
PowerMockito.`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateLibreTestData())
val glucoseStatus: GlucoseStatus = GlucoseStatusProvider(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData!!
Assert.assertEquals(100.0, glucoseStatus.glucose, 0.001) //
Assert.assertEquals(-10.0, glucoseStatus.delta, 0.001)
@ -103,7 +111,8 @@ class GlucoseStatusTest : TestBase() {
@Before
fun initMocking() {
PowerMockito.`when`(dateUtil.now()).thenReturn(1514766900000L + T.mins(1).msecs())
`when`(dateUtil.now()).thenReturn(1514766900000L + T.mins(1).msecs())
`when`(iobCobCalculatorPlugin.ads).thenReturn(autosensDataStore)
}
// [{"mgdl":214,"mills":1521895773113,"device":"xDrip-DexcomG5","direction":"Flat","filtered":191040,"unfiltered":205024,"noise":1,"rssi":100},{"mgdl":219,"mills":1521896073352,"device":"xDrip-DexcomG5","direction":"Flat","filtered":200160,"unfiltered":209760,"noise":1,"rssi":100},{"mgdl":222,"mills":1521896372890,"device":"xDrip-DexcomG5","direction":"Flat","filtered":207360,"unfiltered":212512,"noise":1,"rssi":100},{"mgdl":220,"mills":1521896673062,"device":"xDrip-DexcomG5","direction":"Flat","filtered":211488,"unfiltered":210688,"noise":1,"rssi":100},{"mgdl":193,"mills":1521896972933,"device":"xDrip-DexcomG5","direction":"Flat","filtered":212384,"unfiltered":208960,"noise":1,"rssi":100},{"mgdl":181,"mills":1521897273336,"device":"xDrip-DexcomG5","direction":"SingleDown","filtered":210592,"unfiltered":204320,"noise":1,"rssi":100},{"mgdl":176,"mills":1521897572875,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":206720,"unfiltered":197440,"noise":1,"rssi":100},{"mgdl":168,"mills":1521897872929,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":201024,"unfiltered":187904,"noise":1,"rssi":100},{"mgdl":161,"mills":1521898172814,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":193376,"unfiltered":178144,"noise":1,"rssi":100},{"mgdl":148,"mills":1521898472879,"device":"xDrip-DexcomG5","direction":"SingleDown","filtered":183264,"unfiltered":161216,"noise":1,"rssi":100},{"mgdl":139,"mills":1521898772862,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":170784,"unfiltered":148928,"noise":1,"rssi":100},{"mgdl":132,"mills":1521899072896,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":157248,"unfiltered":139552,"noise":1,"rssi":100},{"mgdl":125,"mills":1521899372834,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":144416,"unfiltered":129616.00000000001,"noise":1,"rssi":100},{"mgdl":128,"mills":1521899973456,"device":"xDrip-DexcomG5","direction":"Flat","filtered":130240.00000000001,"unfiltered":133536,"noise":1,"rssi":100},{"mgdl":132,"mills":1521900573287,"device":"xDrip-DexcomG5","direction":"Flat","filtered":133504,"unfiltered":138720,"noise":1,"rssi":100},{"mgdl":127,"mills":1521900873711,"device":"xDrip-DexcomG5","direction":"Flat","filtered":136480,"unfiltered":132992,"noise":1,"rssi":100},{"mgdl":127,"mills":1521901180151,"device":"xDrip-DexcomG5","direction":"Flat","filtered":136896,"unfiltered":132128,"noise":1,"rssi":100},{"mgdl":125,"mills":1521901473582,"device":"xDrip-DexcomG5","direction":"Flat","filtered":134624,"unfiltered":129696,"noise":1,"rssi":100},{"mgdl":120,"mills":1521901773597,"device":"xDrip-DexcomG5","direction":"Flat","filtered":130704.00000000001,"unfiltered":123376,"noise":1,"rssi":100},{"mgdl":116,"mills":1521902075855,"device":"xDrip-DexcomG5","direction":"Flat","filtered":126272,"unfiltered":118448,"noise":1,"rssi":100}]

View file

@ -1,68 +1,27 @@
package info.nightscout.androidaps.plugins.iob.iobCobCalculator
import android.content.Context
import android.os.PowerManager
import dagger.android.AndroidInjector
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.TestBase
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.interfaces.ActivePluginProvider
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
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.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
import org.junit.Assert
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.util.*
@RunWith(PowerMockRunner::class)
@PrepareForTest(FabricPrivacy::class, AppRepository::class)
class IobCobCalculatorPluginTest : TestBase() {
class AutosensDataStoreTest : TestBase() {
@Mock lateinit var sp: SP
private val rxBus = RxBusWrapper(aapsSchedulers)
@Mock lateinit var resourceHelper: ResourceHelper
@Mock lateinit var profileFunction: ProfileFunction
@Mock lateinit var activePlugin: ActivePluginProvider
@Mock lateinit var sensitivityOref1Plugin: SensitivityOref1Plugin
@Mock lateinit var sensitivityAAPSPlugin: SensitivityAAPSPlugin
@Mock lateinit var sensitivityWeightedAveragePlugin: SensitivityWeightedAveragePlugin
@Mock lateinit var fabricPrivacy: FabricPrivacy
@Mock lateinit var repository: AppRepository
@Mock lateinit var context: Context
@Mock lateinit var powerManager: PowerManager
lateinit var dateUtil: DateUtil
private lateinit var iobCobCalculatorPlugin: IobCobCalculatorPlugin
val injector = HasAndroidInjector {
AndroidInjector {
if (it is IobCobThread) {
it.context = context
it.resourceHelper = resourceHelper
}
}
}
private val autosensDataStore = AutosensDataStore()
@Before
fun mock() {
dateUtil = DateUtil(context)
`when`(context.applicationContext).thenReturn(context)
`when`(context.getSystemService(anyObject())).thenReturn(powerManager)
iobCobCalculatorPlugin = IobCobCalculatorPlugin(injector, aapsLogger, aapsSchedulers, rxBus, sp, resourceHelper, profileFunction, activePlugin, sensitivityOref1Plugin, sensitivityAAPSPlugin, sensitivityWeightedAveragePlugin, fabricPrivacy, dateUtil, repository)
}
@Test
@ -75,8 +34,8 @@ class IobCobCalculatorPluginTest : TestBase() {
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(15).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(10).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
iobCobCalculatorPlugin.bgReadings = bgReadingList
Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData)
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(true, autosensDataStore.isAbout5minData(aapsLogger))
// too much shifted data should return false
bgReadingList.clear()
@ -84,16 +43,16 @@ class IobCobCalculatorPluginTest : TestBase() {
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(15).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(9).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
iobCobCalculatorPlugin.bgReadings = bgReadingList
Assert.assertEquals(false, iobCobCalculatorPlugin.isAbout5minData)
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(false, autosensDataStore.isAbout5minData(aapsLogger))
// too much shifted and missing data should return false
bgReadingList.clear()
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(9).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
iobCobCalculatorPlugin.bgReadings = bgReadingList
Assert.assertEquals(false, iobCobCalculatorPlugin.isAbout5minData)
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(false, autosensDataStore.isAbout5minData(aapsLogger))
// too much shifted and missing data should return false
bgReadingList.clear()
@ -111,8 +70,8 @@ class IobCobCalculatorPluginTest : TestBase() {
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(28).plus(T.secs(0)).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(23).plus(T.secs(0)).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(16).plus(T.secs(36)).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
iobCobCalculatorPlugin.bgReadings = bgReadingList
Assert.assertEquals(false, iobCobCalculatorPlugin.isAbout5minData)
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(false, autosensDataStore.isAbout5minData(aapsLogger))
// slightly shifted data should return true
bgReadingList.clear()
@ -120,16 +79,16 @@ class IobCobCalculatorPluginTest : TestBase() {
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(15).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(10).plus(T.secs(10)).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
iobCobCalculatorPlugin.bgReadings = bgReadingList
Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData)
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(true, autosensDataStore.isAbout5minData(aapsLogger))
// slightly shifted and missing data should return true
bgReadingList.clear()
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(10).plus(T.secs(10)).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
iobCobCalculatorPlugin.bgReadings = bgReadingList
Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData)
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(true, autosensDataStore.isAbout5minData(aapsLogger))
}
@Test
@ -142,24 +101,24 @@ class IobCobCalculatorPluginTest : TestBase() {
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(15).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(10).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
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())
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(true, autosensDataStore.isAbout5minData(aapsLogger))
autosensDataStore.createBucketedData(aapsLogger, dateUtil)
Assert.assertEquals(bgReadingList[0].timestamp, autosensDataStore.bucketedData!![0].timestamp)
Assert.assertEquals(bgReadingList[3].timestamp, autosensDataStore.bucketedData!![3].timestamp)
Assert.assertEquals(bgReadingList.size.toLong(), autosensDataStore.bucketedData!!.size.toLong())
// Missing value should be replaced
bgReadingList.clear()
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(10).plus(T.secs(10)).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
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())
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(true, autosensDataStore.isAbout5minData(aapsLogger))
autosensDataStore.createBucketedData(aapsLogger, dateUtil)
Assert.assertEquals(bgReadingList[0].timestamp, autosensDataStore.bucketedData!![0].timestamp)
Assert.assertEquals(bgReadingList[2].timestamp, autosensDataStore.bucketedData!![3].timestamp)
Assert.assertEquals(bgReadingList.size + 1.toLong(), autosensDataStore.bucketedData!!.size.toLong())
// drift should be cleared
bgReadingList.clear()
@ -168,55 +127,55 @@ class IobCobCalculatorPluginTest : TestBase() {
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(10).msecs() + T.secs(10).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs() + T.secs(10).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(0).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
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())
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(true, autosensDataStore.isAbout5minData(aapsLogger))
autosensDataStore.createBucketedData(aapsLogger, dateUtil)
Assert.assertEquals(T.mins(20).msecs(), autosensDataStore.bucketedData!![0].timestamp)
Assert.assertEquals(T.mins(15).msecs(), autosensDataStore.bucketedData!![1].timestamp)
Assert.assertEquals(T.mins(10).msecs(), autosensDataStore.bucketedData!![2].timestamp)
Assert.assertEquals(T.mins(5).msecs(), autosensDataStore.bucketedData!![3].timestamp)
Assert.assertEquals(bgReadingList.size.toLong(), autosensDataStore.bucketedData!!.size.toLong())
// bucketed data should return null if not enough bg data
bgReadingList.clear()
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(30).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
iobCobCalculatorPlugin.bgReadings = bgReadingList
Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData)
iobCobCalculatorPlugin.createBucketedData()
Assert.assertEquals(null, iobCobCalculatorPlugin.bucketedData)
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(true, autosensDataStore.isAbout5minData(aapsLogger))
autosensDataStore.createBucketedData(aapsLogger, dateUtil)
Assert.assertEquals(null, autosensDataStore.bucketedData)
// data should be reconstructed
bgReadingList.clear()
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(50).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 90.0, timestamp = T.mins(45).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 40.0, timestamp = T.mins(20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
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)
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(true, autosensDataStore.isAbout5minData(aapsLogger))
autosensDataStore.createBucketedData(aapsLogger, dateUtil)
Assert.assertEquals(T.mins(50).msecs(), autosensDataStore.bucketedData!![0].timestamp)
Assert.assertEquals(T.mins(20).msecs(), autosensDataStore.bucketedData!![6].timestamp)
Assert.assertEquals(7, autosensDataStore.bucketedData!!.size.toLong())
Assert.assertEquals(100.0, autosensDataStore.bucketedData!![0].value, 1.0)
Assert.assertEquals(90.0, autosensDataStore.bucketedData!![1].value, 1.0)
Assert.assertEquals(50.0, autosensDataStore.bucketedData!![5].value, 1.0)
Assert.assertEquals(40.0, autosensDataStore.bucketedData!![6].value, 1.0)
// non 5min data should be reconstructed
bgReadingList.clear()
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(50).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 96.0, timestamp = T.mins(48).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 40.0, timestamp = T.mins(20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
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)
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(false, autosensDataStore.isAbout5minData(aapsLogger))
autosensDataStore.createBucketedData(aapsLogger, dateUtil)
Assert.assertEquals(T.mins(50).msecs(), autosensDataStore.bucketedData!![0].timestamp)
Assert.assertEquals(T.mins(20).msecs(), autosensDataStore.bucketedData!![6].timestamp)
Assert.assertEquals(7, autosensDataStore.bucketedData!!.size.toLong())
Assert.assertEquals(100.0, autosensDataStore.bucketedData!![0].value, 1.0)
Assert.assertEquals(90.0, autosensDataStore.bucketedData!![1].value, 1.0)
Assert.assertEquals(50.0, autosensDataStore.bucketedData!![5].value, 1.0)
Assert.assertEquals(40.0, autosensDataStore.bucketedData!![6].value, 1.0)
}
@Test
@ -224,9 +183,9 @@ class IobCobCalculatorPluginTest : TestBase() {
val bgReadingList: MutableList<GlucoseValue> = ArrayList()
//bucketed data should be null if no bg data available
iobCobCalculatorPlugin.bgReadings = ArrayList()
iobCobCalculatorPlugin.createBucketedData()
Assert.assertEquals(null, iobCobCalculatorPlugin.bucketedData)
autosensDataStore.bgReadings = ArrayList()
autosensDataStore.createBucketedData(aapsLogger, dateUtil)
Assert.assertEquals(null, autosensDataStore.bucketedData)
// real data gap test
bgReadingList.clear()
@ -258,12 +217,12 @@ class IobCobCalculatorPluginTest : TestBase() {
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T03:54:56Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T03:50:03Z"), 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"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
iobCobCalculatorPlugin.bgReadings = bgReadingList
iobCobCalculatorPlugin.referenceTime = -1
Assert.assertEquals(true, iobCobCalculatorPlugin.isAbout5minData)
iobCobCalculatorPlugin.createBucketedData()
Assert.assertEquals(dateUtil.fromISODateString("2018-09-05T13:34:57Z"), iobCobCalculatorPlugin.bucketedData!![0].timestamp)
Assert.assertEquals(dateUtil.fromISODateString("2018-09-05T03:44:57Z"), iobCobCalculatorPlugin.bucketedData!![iobCobCalculatorPlugin.bucketedData!!.size - 1].timestamp)
autosensDataStore.bgReadings = bgReadingList
autosensDataStore.referenceTime = -1
Assert.assertEquals(true, autosensDataStore.isAbout5minData(aapsLogger))
autosensDataStore.createBucketedData(aapsLogger, dateUtil)
Assert.assertEquals(dateUtil.fromISODateString("2018-09-05T13:34:57Z"), autosensDataStore.bucketedData!![0].timestamp)
Assert.assertEquals(dateUtil.fromISODateString("2018-09-05T03:44:57Z"), autosensDataStore.bucketedData!![autosensDataStore.bucketedData!!.size - 1].timestamp)
// 5min 4sec data
bgReadingList.clear()
@ -288,20 +247,20 @@ class IobCobCalculatorPluginTest : TestBase() {
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T05:02:26Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T04:57:21Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T04:52:17Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
iobCobCalculatorPlugin.bgReadings = bgReadingList
Assert.assertEquals(false, iobCobCalculatorPlugin.isAbout5minData)
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(false, autosensDataStore.isAbout5minData(aapsLogger))
}
@Test
fun bgReadingsTest() {
val bgReadingList: List<GlucoseValue> = ArrayList()
iobCobCalculatorPlugin.bgReadings = bgReadingList
Assert.assertEquals(bgReadingList, iobCobCalculatorPlugin.bgReadings)
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(bgReadingList, autosensDataStore.bgReadings)
}
@Test
fun roundUpTimeTest() {
Assert.assertEquals(T.mins(3).msecs(), iobCobCalculatorPlugin.roundUpTime(T.secs(155).msecs()))
Assert.assertEquals(T.mins(3).msecs(), autosensDataStore.roundUpTime(T.secs(155).msecs()))
}
@Test
@ -311,12 +270,12 @@ class IobCobCalculatorPluginTest : TestBase() {
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(15).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(10).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
iobCobCalculatorPlugin.bgReadings = bgReadingList
Assert.assertEquals(T.mins(10).msecs(), iobCobCalculatorPlugin.findNewer(T.mins(8).msecs())!!.timestamp)
Assert.assertEquals(T.mins(5).msecs(), iobCobCalculatorPlugin.findNewer(T.mins(5).msecs())!!.timestamp)
Assert.assertEquals(T.mins(10).msecs(), iobCobCalculatorPlugin.findNewer(T.mins(10).msecs())!!.timestamp)
Assert.assertEquals(T.mins(20).msecs(), iobCobCalculatorPlugin.findNewer(T.mins(20).msecs())!!.timestamp)
Assert.assertEquals(null, iobCobCalculatorPlugin.findNewer(T.mins(22).msecs()))
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(T.mins(10).msecs(), autosensDataStore.findNewer(T.mins(8).msecs())!!.timestamp)
Assert.assertEquals(T.mins(5).msecs(), autosensDataStore.findNewer(T.mins(5).msecs())!!.timestamp)
Assert.assertEquals(T.mins(10).msecs(), autosensDataStore.findNewer(T.mins(10).msecs())!!.timestamp)
Assert.assertEquals(T.mins(20).msecs(), autosensDataStore.findNewer(T.mins(20).msecs())!!.timestamp)
Assert.assertEquals(null, autosensDataStore.findNewer(T.mins(22).msecs()))
}
@Test
@ -326,20 +285,20 @@ class IobCobCalculatorPluginTest : TestBase() {
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(15).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(10).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
iobCobCalculatorPlugin.bgReadings = bgReadingList
Assert.assertEquals(T.mins(5).msecs(), iobCobCalculatorPlugin.findOlder(T.mins(8).msecs())!!.timestamp)
Assert.assertEquals(T.mins(5).msecs(), iobCobCalculatorPlugin.findOlder(T.mins(5).msecs())!!.timestamp)
Assert.assertEquals(T.mins(10).msecs(), iobCobCalculatorPlugin.findOlder(T.mins(10).msecs())!!.timestamp)
Assert.assertEquals(T.mins(20).msecs(), iobCobCalculatorPlugin.findOlder(T.mins(20).msecs())!!.timestamp)
Assert.assertEquals(null, iobCobCalculatorPlugin.findOlder(T.mins(4).msecs()))
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(T.mins(5).msecs(), autosensDataStore.findOlder(T.mins(8).msecs())!!.timestamp)
Assert.assertEquals(T.mins(5).msecs(), autosensDataStore.findOlder(T.mins(5).msecs())!!.timestamp)
Assert.assertEquals(T.mins(10).msecs(), autosensDataStore.findOlder(T.mins(10).msecs())!!.timestamp)
Assert.assertEquals(T.mins(20).msecs(), autosensDataStore.findOlder(T.mins(20).msecs())!!.timestamp)
Assert.assertEquals(null, autosensDataStore.findOlder(T.mins(4).msecs()))
}
@Test
fun findPreviousTimeFromBucketedDataTest() {
val bgReadingList: MutableList<GlucoseValue> = ArrayList()
iobCobCalculatorPlugin.bgReadings = bgReadingList
iobCobCalculatorPlugin.createBucketedData()
Assert.assertEquals(null, iobCobCalculatorPlugin.findPreviousTimeFromBucketedData(1000))
autosensDataStore.bgReadings = bgReadingList
autosensDataStore.createBucketedData(aapsLogger, dateUtil)
Assert.assertEquals(null, autosensDataStore.findPreviousTimeFromBucketedData(1000))
// Super data should not be touched
bgReadingList.clear()
@ -347,11 +306,11 @@ class IobCobCalculatorPluginTest : TestBase() {
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(15).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(10).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
iobCobCalculatorPlugin.bgReadings = bgReadingList
iobCobCalculatorPlugin.createBucketedData()
Assert.assertEquals(null, iobCobCalculatorPlugin.findPreviousTimeFromBucketedData(T.mins(4).msecs()))
Assert.assertEquals(T.mins(5).msecs(), iobCobCalculatorPlugin.findPreviousTimeFromBucketedData(T.mins(6).msecs()))
Assert.assertEquals(T.mins(20).msecs(), iobCobCalculatorPlugin.findPreviousTimeFromBucketedData(T.mins(20).msecs()))
Assert.assertEquals(T.mins(20).msecs(), iobCobCalculatorPlugin.findPreviousTimeFromBucketedData(T.mins(25).msecs()))
autosensDataStore.bgReadings = bgReadingList
autosensDataStore.createBucketedData(aapsLogger, dateUtil)
Assert.assertEquals(null, autosensDataStore.findPreviousTimeFromBucketedData(T.mins(4).msecs()))
Assert.assertEquals(T.mins(5).msecs(), autosensDataStore.findPreviousTimeFromBucketedData(T.mins(6).msecs()))
Assert.assertEquals(T.mins(20).msecs(), autosensDataStore.findPreviousTimeFromBucketedData(T.mins(20).msecs()))
Assert.assertEquals(T.mins(20).msecs(), autosensDataStore.findPreviousTimeFromBucketedData(T.mins(25).msecs()))
}
}