cache last DataStore object for bolus wizard

This commit is contained in:
Milos Kozak 2023-01-12 10:44:01 +01:00
parent c802e7e4a2
commit 86edb3dfae
16 changed files with 95 additions and 42 deletions

View file

@ -32,11 +32,10 @@ interface IobCobCalculator {
/**
* Calculate CobInfo to now()
*
* @param waitForCalculationFinish access autosens data synchronized (wait for result if calculation is running)
* @param reason caller identification
* @return CobInfo
*/
fun getCobInfo(waitForCalculationFinish: Boolean, reason: String): CobInfo
fun getCobInfo(reason: String): CobInfo
/**
* Calculate IobTotal from boluses and extended boluses to now().

View file

@ -107,7 +107,7 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec
fun isActive(): Boolean = time.secondsFromMidnight() >= validFrom() && time.secondsFromMidnight() <= validTo() && forDevice(DEVICE_PHONE)
fun doCalc(profile: Profile, profileName: String, lastBG: InMemoryGlucoseValue, _synchronized: Boolean): BolusWizard {
fun doCalc(profile: Profile, profileName: String, lastBG: InMemoryGlucoseValue): BolusWizard {
val dbRecord = persistenceLayer.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet()
val tempTarget = if (dbRecord is ValueWrapper.Existing) dbRecord.value else null
//BG
@ -117,7 +117,7 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec
}
// COB
val cob =
if (useCOB() == YES) iobCobCalculator.getCobInfo(_synchronized, "QuickWizard COB").displayCob ?: 0.0
if (useCOB() == YES) iobCobCalculator.getCobInfo("QuickWizard COB").displayCob ?: 0.0
else 0.0
// Bolus IOB
var bolusIOB = false

View file

@ -219,7 +219,7 @@ class OverviewDataImpl @Inject constructor(
override fun bolusIob(iobCobCalculator: IobCobCalculator): IobTotal = iobCobCalculator.calculateIobFromBolus().round()
override fun basalIob(iobCobCalculator: IobCobCalculator): IobTotal = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round()
override fun cobInfo(iobCobCalculator: IobCobCalculator): CobInfo = iobCobCalculator.getCobInfo(true, "Overview COB")
override fun cobInfo(iobCobCalculator: IobCobCalculator): CobInfo = iobCobCalculator.getCobInfo("Overview COB")
override val lastCarbsTime: Long
get() = repository.getLastCarbsRecordWrapped().blockingGet().let { lastCarbs ->

View file

@ -38,7 +38,7 @@ class TriggerCOB(injector: HasAndroidInjector) : Trigger(injector) {
}
override fun shouldRun(): Boolean {
val cobInfo = iobCobCalculator.getCobInfo(false, "AutomationTriggerCOB")
val cobInfo = iobCobCalculator.getCobInfo("AutomationTriggerCOB")
if (cobInfo.displayCob == null) {
return if (comparator.value === Comparator.Compare.IS_NOT_AVAILABLE) {
aapsLogger.debug(LTag.AUTOMATION, "Ready for execution: " + friendlyDescription())

View file

@ -21,7 +21,7 @@ class TriggerCOBTest : TriggerTestBase() {
@Test fun shouldRunTest() {
// COB value is 6
`when`(iobCobCalculator.getCobInfo(false, "AutomationTriggerCOB")).thenReturn(CobInfo(0, 6.0, 2.0))
`when`(iobCobCalculator.getCobInfo("AutomationTriggerCOB")).thenReturn(CobInfo(0, 6.0, 2.0))
var t: TriggerCOB = TriggerCOB(injector).setValue(1.0).comparator(Comparator.Compare.IS_EQUAL)
Assert.assertFalse(t.shouldRun())
t = TriggerCOB(injector).setValue(6.0).comparator(Comparator.Compare.IS_EQUAL)

View file

@ -133,7 +133,7 @@ class DataBroadcastPlugin @Inject constructor(
bundle.putDouble("basalIob", basalIob.basaliob)
bundle.putDouble("iob", bolusIob.iob + basalIob.basaliob) // total IOB
val cob = iobCobCalculator.getCobInfo(false, "broadcast")
val cob = iobCobCalculator.getCobInfo("broadcast")
bundle.putDouble("cob", cob.displayCob ?: -1.0) // COB [g] or -1 if N/A
bundle.putDouble("futureCarbs", cob.futureCarbs) // future scheduled carbs
}

View file

@ -511,7 +511,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
val quickWizardEntry = quickWizard.getActive()
if (quickWizardEntry != null && actualBg != null && profile != null) {
binding.buttonsLayout.quickWizardButton.visibility = View.VISIBLE
val wizard = quickWizardEntry.doCalc(profile, profileName, actualBg, true)
val wizard = quickWizardEntry.doCalc(profile, profileName, actualBg)
if (wizard.calculatedTotalInsulin > 0.0 && quickWizardEntry.carbs() > 0.0) {
val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(quickWizardEntry.carbs())).value()
activity?.let {
@ -539,7 +539,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
_binding ?: return@runOnUiThread
if (quickWizardEntry != null && lastBG != null && profile != null && pump.isInitialized() && !pump.isSuspended() && !loop.isDisconnected) {
binding.buttonsLayout.quickWizardButton.visibility = View.VISIBLE
val wizard = quickWizardEntry.doCalc(profile, profileName, lastBG, false)
val wizard = quickWizardEntry.doCalc(profile, profileName, lastBG)
binding.buttonsLayout.quickWizardButton.text = quickWizardEntry.buttonText() + "\n" + rh.gs(info.nightscout.core.graph.R.string.format_carbs, quickWizardEntry.carbs()) +
" " + rh.gs(info.nightscout.interfaces.R.string.format_insulin_units, wizard.calculatedTotalInsulin)
if (wizard.calculatedTotalInsulin <= 0) binding.buttonsLayout.quickWizardButton.visibility = View.GONE

View file

@ -148,12 +148,10 @@ class PersistentNotificationPlugin @Inject constructor(
val basalIob = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round()
line2 =
rh.gs(info.nightscout.core.ui.R.string.treatments_iob_label_string) + " " + DecimalFormatter.to2Decimal(bolusIob.iob + basalIob.basaliob) + "U " + rh.gs(info.nightscout.core.ui.R.string.cob) + ": " + iobCobCalculator.getCobInfo(
false,
"PersistentNotificationPlugin"
).generateCOBString()
val line2aa =
rh.gs(info.nightscout.core.ui.R.string.treatments_iob_label_string) + " " + DecimalFormatter.to2Decimal(bolusIob.iob + basalIob.basaliob) + "U. " + rh.gs(info.nightscout.core.ui.R.string.cob) + ": " + iobCobCalculator.getCobInfo(
false,
"PersistentNotificationPlugin"
).generateCOBString() + "."
line3 = DecimalFormatter.to2Decimal(pump.baseBasalRate) + " U/h"

View file

@ -365,7 +365,7 @@ class SmsCommunicatorPlugin @Inject constructor(
if (glucoseStatus != null) reply += rh.gs(R.string.sms_delta) + " " + Profile.toUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units) + " " + units + ", "
val bolusIob = iobCobCalculator.calculateIobFromBolus().round()
val basalIob = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round()
val cobInfo = iobCobCalculator.getCobInfo(false, "SMS COB")
val cobInfo = iobCobCalculator.getCobInfo("SMS COB")
reply += (rh.gs(R.string.sms_iob) + " " + DecimalFormatter.to2Decimal(bolusIob.iob + basalIob.basaliob) + "U ("
+ rh.gs(R.string.sms_bolus) + " " + DecimalFormatter.to2Decimal(bolusIob.iob) + "U "
+ rh.gs(R.string.sms_basal) + " " + DecimalFormatter.to2Decimal(basalIob.basaliob) + "U), "

View file

@ -16,7 +16,13 @@ import info.nightscout.core.wizard.BolusWizard
import info.nightscout.core.wizard.QuickWizard
import info.nightscout.core.wizard.QuickWizardEntry
import info.nightscout.database.ValueWrapper
import info.nightscout.database.entities.*
import info.nightscout.database.entities.Bolus
import info.nightscout.database.entities.GlucoseValue
import info.nightscout.database.entities.TemporaryBasal
import info.nightscout.database.entities.TemporaryTarget
import info.nightscout.database.entities.TotalDailyDose
import info.nightscout.database.entities.UserEntry
import info.nightscout.database.entities.ValueWithUnit
import info.nightscout.database.entities.interfaces.end
import info.nightscout.database.impl.AppRepository
import info.nightscout.database.impl.transactions.CancelCurrentTemporaryTargetIfAnyTransaction
@ -60,7 +66,9 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
import java.util.Date
import java.util.LinkedList
import java.util.Locale
import java.util.concurrent.TimeUnit
import java.util.stream.Collectors
import javax.inject.Inject
@ -369,7 +377,7 @@ class DataHandlerMobile @Inject constructor(
sendError(rh.gs(info.nightscout.core.ui.R.string.wizard_no_actual_bg))
return
}
val cobInfo = iobCobCalculator.getCobInfo(false, "Wizard wear")
val cobInfo = iobCobCalculator.getCobInfo("Wizard wear")
if (cobInfo.displayCob == null) {
sendError(rh.gs(info.nightscout.core.ui.R.string.wizard_no_cob))
return
@ -436,7 +444,7 @@ class DataHandlerMobile @Inject constructor(
sendError(rh.gs(info.nightscout.core.ui.R.string.wizard_no_active_profile))
return
}
val cobInfo = iobCobCalculator.getCobInfo(false, "QuickWizard wear")
val cobInfo = iobCobCalculator.getCobInfo("QuickWizard wear")
if (cobInfo.displayCob == null) {
sendError(rh.gs(info.nightscout.core.ui.R.string.wizard_no_cob))
return
@ -447,7 +455,7 @@ class DataHandlerMobile @Inject constructor(
return
}
val wizard = quickWizardEntry.doCalc(profile, profileName, actualBg, true)
val wizard = quickWizardEntry.doCalc(profile, profileName, actualBg)
val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(quickWizardEntry.carbs())).value()
if (carbsAfterConstraints != quickWizardEntry.carbs()) {
@ -877,7 +885,7 @@ class DataHandlerMobile @Inject constructor(
val basalIob = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round()
iobSum = DecimalFormatter.to2Decimal(bolusIob.iob + basalIob.basaliob)
iobDetail = "(${DecimalFormatter.to2Decimal(bolusIob.iob)}|${DecimalFormatter.to2Decimal(basalIob.basaliob)})"
cobString = iobCobCalculator.getCobInfo(false, "WatcherUpdaterService").generateCOBString()
cobString = iobCobCalculator.getCobInfo("WatcherUpdaterService").generateCOBString()
currentBasal =
iobCobCalculator.getTempBasalIncludingConvertedExtended(System.currentTimeMillis())?.toStringShort() ?: rh.gs(info.nightscout.core.ui.R.string.pump_base_basal_rate, profile.getBasal())

View file

@ -151,7 +151,7 @@ class StatusLinePlugin @Inject constructor(
status += " " + (if (bgi >= 0) "+" else "") + DecimalFormatter.to2Decimal(bgi)
}
// COB
status += " " + iobCobCalculator.getCobInfo(false, "StatusLinePlugin").generateCOBString()
status += " " + iobCobCalculator.getCobInfo("StatusLinePlugin").generateCOBString()
return status
}
}

View file

@ -292,10 +292,8 @@ class IobCobCalculatorPlugin @Inject constructor(
return ads.getLastAutosensData(reason, aapsLogger, dateUtil)
}
override fun getCobInfo(waitForCalculationFinish: Boolean, reason: String): CobInfo {
val autosensData =
if (waitForCalculationFinish) getLastAutosensDataWithWaitForCalculationFinish(reason)
else ads.getLastAutosensData(reason, aapsLogger, dateUtil)
override fun getCobInfo(reason: String): CobInfo {
val autosensData = ads.getLastAutosensData(reason, aapsLogger, dateUtil)
var displayCob: Double? = null
var futureCarbs = 0.0
val now = dateUtil.now()

View file

@ -116,30 +116,34 @@ class AutosensDataStoreObject : AutosensDataStore {
}
}
// during recalculation autosensDataTable is cleared and not available
// for providing COB, which is an serious issue in BolusWizard
// So let save last value after every calculation and use it
// if autosensDataTable is not available
var storedLastAutosensResult: AutosensData? = null
get() = field?.let { if (it.time < System.currentTimeMillis() - 11 * 60 * 1000) it else null }
override 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
return storedLastAutosensResult
}
val data: AutosensData? = try {
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
return storedLastAutosensResult
}
if (data == null) {
aapsLogger.error("AUTOSENSDATA null: data==null")
return null
}
return if (data.time < System.currentTimeMillis() - 11 * 60 * 1000) {
return if (data.time < dateUtil.now() - 11 * 60 * 1000) {
aapsLogger.debug(LTag.AUTOSENS) { "AUTOSENSDATA null: data is old ($reason) size()=${autosensDataTable.size()} lastData=${dateUtil.dateAndTimeAndSecondsString(data.time)}" }
null
storedLastAutosensResult
} else {
aapsLogger.debug(LTag.AUTOSENS) { "AUTOSENSDATA ($reason) $data" }
storedLastAutosensResult = data
data
}
}
@ -152,11 +156,8 @@ class AutosensDataStoreObject : AutosensDataStore {
}
var diff = abs(someTime - referenceTime)
diff %= T.mins(5).msecs()
if (diff > T.mins(2).plus(T.secs(30)).msecs()){
return someTime + abs(diff - T.mins(5).msecs()) // Adjust to the future
} else {
return someTime - diff // adjust to the past
}
return if (diff > T.mins(2).plus(T.secs(30)).msecs()) someTime + abs(diff - T.mins(5).msecs()) // Adjust to the future
else someTime - diff // adjust to the past
}
fun isAbout5minData(aapsLogger: AAPSLogger): Boolean {

View file

@ -97,7 +97,7 @@ class SmsCommunicatorPluginTest : TestBaseWithProfile() {
val bgList: MutableList<GlucoseValue> = ArrayList()
bgList.add(reading)
`when`(iobCobCalculator.getCobInfo(false, "SMS COB")).thenReturn(CobInfo(0, 10.0, 2.0))
`when`(iobCobCalculator.getCobInfo("SMS COB")).thenReturn(CobInfo(0, 10.0, 2.0))
`when`(iobCobCalculator.ads).thenReturn(autosensDataStore)
`when`(autosensDataStore.lastBg()).thenReturn(InMemoryGlucoseValue(reading))

View file

@ -1,22 +1,48 @@
package info.nightscout.plugins.iob
import android.content.Context
import androidx.collection.LongSparseArray
import dagger.android.AndroidInjector
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.TestBase
import info.nightscout.database.entities.GlucoseValue
import info.nightscout.interfaces.aps.AutosensData
import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.plugins.iob.iobCobCalculator.data.AutosensDataObject
import info.nightscout.plugins.iob.iobCobCalculator.data.AutosensDataStoreObject
import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil
import info.nightscout.shared.utils.T
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.Mock
import org.mockito.Mockito
class AutosensDataStoreTest : TestBase() {
@Mock lateinit var context: Context
@Mock lateinit var sp: SP
@Mock lateinit var rh: ResourceHelper
@Mock lateinit var profileFunction: ProfileFunction
@Mock lateinit var dateUtilMocked: DateUtil
private lateinit var dateUtil: DateUtil
private val autosensDataStore = info.nightscout.plugins.iob.iobCobCalculator.data.AutosensDataStoreObject()
private val autosensDataStore = AutosensDataStoreObject()
private val injector = HasAndroidInjector {
AndroidInjector {
if (it is AutosensDataObject) {
it.aapsLogger = aapsLogger
it.sp = sp
it.rh = rh
it.profileFunction = profileFunction
it.dateUtil = dateUtilMocked
}
}
}
@BeforeEach
fun mock() {
@ -1264,7 +1290,7 @@ class AutosensDataStoreTest : TestBase() {
Assertions.assertEquals(67.0, autosensDataStore.bucketedData!![3].value, 1.0) // Recalculated data to 30min
Assertions.assertEquals(45.0, autosensDataStore.bucketedData!![5].value, 1.0) // Recalculated data to 20min
// non 5min data without referenceTime set, should allign the data to the time of the last reading
// non 5min data without referenceTime set, should align the data to the time of the last reading
autosensDataStore.referenceTime = -1
bgReadingList.clear()
bgReadingList.add(
@ -1491,4 +1517,27 @@ class AutosensDataStoreTest : TestBase() {
Assertions.assertEquals(T.mins(20).msecs(), autosensDataStore.findPreviousTimeFromBucketedData(T.mins(20).msecs()))
Assertions.assertEquals(T.mins(20).msecs(), autosensDataStore.findPreviousTimeFromBucketedData(T.mins(25).msecs()))
}
@Test
fun getLastAutosensDataTest() {
val now = 10000000L
Mockito.`when`(dateUtilMocked.now()).thenReturn(now)
val ads = AutosensDataStoreObject()
ads.storedLastAutosensResult = AutosensDataObject(injector).apply { time = now - 10 }
// empty array, return last stored
ads.autosensDataTable = LongSparseArray<AutosensData>()
Assertions.assertEquals(now - 10, ads.getLastAutosensData("test", aapsLogger, dateUtilMocked)?.time)
// data is there, return it
ads.autosensDataTable.append(now - 1, AutosensDataObject(injector).apply { time = now - 1 })
Assertions.assertEquals(now - 1, ads.getLastAutosensData("test", aapsLogger, dateUtilMocked)?.time)
// and latest value should be saved
Assertions.assertEquals(now - 1, ads.storedLastAutosensResult?.time)
// data is old, return last stored
ads.storedLastAutosensResult = AutosensDataObject(injector).apply { time = now - 1 }
ads.autosensDataTable = LongSparseArray<AutosensData>()
ads.autosensDataTable.append(now - T.mins(20).msecs(), AutosensDataObject(injector).apply { time = now - T.mins(20).msecs() })
Assertions.assertEquals(now - 1, ads.getLastAutosensData("test", aapsLogger, dateUtilMocked)?.time)
}
}

View file

@ -434,7 +434,7 @@ class WizardDialog : DaggerDialogFragment() {
// COB
var cob = 0.0
if (binding.cobCheckbox.isChecked) {
val cobInfo = iobCobCalculator.getCobInfo(false, "Wizard COB")
val cobInfo = iobCobCalculator.getCobInfo("Wizard COB")
cobInfo.displayCob?.let { cob = it }
}