MVVM like for overview

This commit is contained in:
Milos Kozak 2021-05-17 21:05:24 +02:00
parent 3040be317c
commit a1c1355ce6
28 changed files with 1455 additions and 1138 deletions

View file

@ -75,11 +75,11 @@ class CompatDBHelper @Inject constructor(
rxBus.send(EventFoodDatabaseChanged()) rxBus.send(EventFoodDatabaseChanged())
} }
it.filterIsInstance<ProfileSwitch>().firstOrNull()?.let { it.filterIsInstance<ProfileSwitch>().firstOrNull()?.let {
aapsLogger.debug(LTag.DATABASE, "Firing EventProfileNeedsUpdate") aapsLogger.debug(LTag.DATABASE, "Firing EventProfileSwitchChanged")
rxBus.send(EventProfileSwitchChanged()) rxBus.send(EventProfileSwitchChanged())
} }
it.filterIsInstance<EffectiveProfileSwitch>().firstOrNull()?.let { it.filterIsInstance<EffectiveProfileSwitch>().firstOrNull()?.let {
aapsLogger.debug(LTag.DATABASE, "Firing EventProfileNeedsUpdate") aapsLogger.debug(LTag.DATABASE, "Firing EventProfileSwitchChanged")
rxBus.send(EventProfileSwitchChanged()) rxBus.send(EventProfileSwitchChanged())
} }
} }

View file

@ -279,7 +279,7 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() {
if (destroyed) return@launch if (destroyed) return@launch
binding.date.text = dateUtil.dateAndTimeString(start) binding.date.text = dateUtil.dateAndTimeString(start)
binding.zoom.text = rangeToDisplay.toString() binding.zoom.text = rangeToDisplay.toString()
val graphData = GraphData(injector, binding.bggraph, iobCobCalculatorPluginHistory) val graphData = GraphData(injector, binding.bggraph)
val secondaryGraphsData: ArrayList<GraphData> = ArrayList() val secondaryGraphsData: ArrayList<GraphData> = ArrayList()
// do preparation in different thread // do preparation in different thread
@ -293,29 +293,29 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() {
graphData.addInRangeArea(fromTime, toTime, lowLine, highLine) graphData.addInRangeArea(fromTime, toTime, lowLine, highLine)
// **** BG **** // **** BG ****
graphData.addBgReadings(fromTime, toTime, highLine, null) // graphData.addBgReadings(fromTime, toTime, highLine, null)
if (buildHelper.isDev()) graphData.addBucketedData(fromTime, toTime) // if (buildHelper.isDev()) graphData.addBucketedData(fromTime, toTime)
// add target line // add target line
graphData.addTargetLine(fromTime, toTime, profile, null) // graphData.addTargetLine(fromTime, toTime, profile, null)
// **** NOW line **** // **** NOW line ****
graphData.addNowLine(pointer) graphData.addNowLine(pointer)
if (!bgOnly) { if (!bgOnly) {
// Treatments // Treatments
graphData.addTreatments(fromTime, toTime) // graphData.addTreatments(fromTime, toTime)
if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal]) if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal])
graphData.addActivity(fromTime, toTime, false, 0.8) // graphData.addActivity(fromTime, toTime, false, 0.8)
// add basal data // add basal data
if (pump.pumpDescription.isTempBasalCapable && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal]) { if (pump.pumpDescription.isTempBasalCapable && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal]) {
graphData.addBasals(fromTime, toTime, lowLine / graphData.maxY / 1.2) // graphData.addBasals(fromTime, toTime, lowLine / graphData.maxY / 1.2)
} }
// ------------------ 2nd graph // ------------------ 2nd graph
synchronized(graphLock) { synchronized(graphLock) {
for (g in 0 until secondaryGraphs.size) { for (g in 0 until secondaryGraphs.size) {
val secondGraphData = GraphData(injector, secondaryGraphs[g], iobCobCalculatorPluginHistory) val secondGraphData = GraphData(injector, secondaryGraphs[g])
var useIobForScale = false var useIobForScale = false
var useCobForScale = false var useCobForScale = false
var useDevForScale = false var useDevForScale = false
@ -336,13 +336,13 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() {
val alignIobScale = menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] val alignIobScale = menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]
val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]
if (menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal]) secondGraphData.addAbsIob(fromTime, toTime, useABSForScale, 1.0) // if (menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal]) secondGraphData.addAbsIob(fromTime, toTime, useABSForScale, 1.0)
if (menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]) secondGraphData.addIob(fromTime, toTime, useIobForScale, 1.0, menuChartSettings[g + 1][OverviewMenus.CharType.PRE.ordinal], alignIobScale) // if (menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]) secondGraphData.addIob(fromTime, toTime, useIobForScale, 1.0, menuChartSettings[g + 1][OverviewMenus.CharType.PRE.ordinal], alignIobScale)
if (menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal]) secondGraphData.addCob(fromTime, toTime, useCobForScale, if (useCobForScale) 1.0 else 0.5) // if (menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal]) secondGraphData.addCob(fromTime, toTime, useCobForScale, if (useCobForScale) 1.0 else 0.5)
if (menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal]) secondGraphData.addDeviations(fromTime, toTime, useDevForScale, 1.0, alignDevBgiScale) // if (menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal]) secondGraphData.addDeviations(fromTime, toTime, useDevForScale, 1.0, alignDevBgiScale)
if (menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal]) secondGraphData.addRatio(fromTime, toTime, useRatioForScale, 1.0) // if (menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal]) secondGraphData.addRatio(fromTime, toTime, useRatioForScale, 1.0)
if (menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]) secondGraphData.addMinusBGI(fromTime, toTime, useBGIForScale, if (alignDevBgiScale) 1.0 else 0.8, alignDevBgiScale) // if (menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]) secondGraphData.addMinusBGI(fromTime, toTime, useBGIForScale, if (alignDevBgiScale) 1.0 else 0.8, alignDevBgiScale)
if (menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] && buildHelper.isDev()) secondGraphData.addDeviationSlope(fromTime, toTime, useDSForScale, 1.0) // if (menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] && buildHelper.isDev()) secondGraphData.addDeviationSlope(fromTime, toTime, useDSForScale, 1.0)
// set manual x bounds to have nice steps // set manual x bounds to have nice steps
secondGraphData.formatAxis(fromTime, toTime) secondGraphData.formatAxis(fromTime, toTime)

View file

@ -0,0 +1,5 @@
package info.nightscout.androidaps.plugins.aps.events
import info.nightscout.androidaps.events.Event
class EventLoopInvoked : Event()

View file

@ -54,6 +54,7 @@ import info.nightscout.androidaps.extensions.buildDeviceStatus
import info.nightscout.androidaps.extensions.convertedToAbsolute import info.nightscout.androidaps.extensions.convertedToAbsolute
import info.nightscout.androidaps.extensions.convertedToPercent import info.nightscout.androidaps.extensions.convertedToPercent
import info.nightscout.androidaps.extensions.plannedRemainingMinutes import info.nightscout.androidaps.extensions.plannedRemainingMinutes
import info.nightscout.androidaps.plugins.aps.events.EventLoopInvoked
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.sharedPreferences.SP import info.nightscout.androidaps.utils.sharedPreferences.SP
@ -286,7 +287,7 @@ open class LoopPlugin @Inject constructor(
if (apsResult == null) { if (apsResult == null) {
rxBus.send(EventLoopSetLastRunGui(resourceHelper.gs(R.string.noapsselected))) rxBus.send(EventLoopSetLastRunGui(resourceHelper.gs(R.string.noapsselected)))
return return
} } else rxBus.send(EventLoopInvoked())
// Prepare for pumps using % basals // Prepare for pumps using % basals
if (pump.pumpDescription.tempBasalStyle == PumpDescription.PERCENT && allowPercentage()) { if (pump.pumpDescription.tempBasalStyle == PumpDescription.PERCENT && allowPercentage()) {

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.plugins.configBuilder package info.nightscout.androidaps.plugins.configBuilder
import androidx.collection.LongSparseArray
import info.nightscout.androidaps.Constants import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.core.R import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.data.ProfileSealed import info.nightscout.androidaps.data.ProfileSealed
@ -35,6 +36,8 @@ class ProfileFunctionImplementation @Inject constructor(
private val dateUtil: DateUtil private val dateUtil: DateUtil
) : ProfileFunction { ) : ProfileFunction {
val cache = LongSparseArray<Profile>()
private val disposable = CompositeDisposable() private val disposable = CompositeDisposable()
override fun getProfileName(): String = override fun getProfileName(): String =
@ -65,10 +68,20 @@ class ProfileFunctionImplementation @Inject constructor(
getProfile(dateUtil.now()) getProfile(dateUtil.now())
override fun getProfile(time: Long): Profile? { override fun getProfile(time: Long): Profile? {
val rounded = time - time % 1000
val cached = cache[rounded]
if (cached != null) {
// aapsLogger.debug("XXXXXXXXXXXXXXX HIT getProfile for $time $rounded")
return cached
}
// aapsLogger.debug("XXXXXXXXXXXXXXX getProfile called for $time") // aapsLogger.debug("XXXXXXXXXXXXXXX getProfile called for $time")
val ps = repository.getEffectiveProfileSwitchActiveAt(time).blockingGet() val ps = repository.getEffectiveProfileSwitchActiveAt(time).blockingGet()
return if (ps is ValueWrapper.Existing) ProfileSealed.EPS(ps.value) if (ps is ValueWrapper.Existing) {
else null val sealed = ProfileSealed.EPS(ps.value)
cache.put(rounded, sealed)
return sealed
}
return null
} }
override fun getRequestedProfile(): ProfileSwitch? = repository.getActiveProfileSwitch(dateUtil.now()) override fun getRequestedProfile(): ProfileSwitch? = repository.getActiveProfileSwitch(dateUtil.now())

View file

@ -11,7 +11,6 @@ import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import dagger.android.support.DaggerFragment import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.Constants import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.ErrorHelperActivity import info.nightscout.androidaps.activities.ErrorHelperActivity
@ -21,13 +20,18 @@ import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.dialogs.* import info.nightscout.androidaps.dialogs.*
import info.nightscout.androidaps.events.* import info.nightscout.androidaps.events.EventCustomActionsChanged
import info.nightscout.androidaps.events.EventExtendedBolusChange
import info.nightscout.androidaps.events.EventInitializationChanged
import info.nightscout.androidaps.events.EventTempBasalChange
import info.nightscout.androidaps.events.EventTherapyEventChange
import info.nightscout.androidaps.extensions.toStringMedium import info.nightscout.androidaps.extensions.toStringMedium
import info.nightscout.androidaps.extensions.toStringShort import info.nightscout.androidaps.extensions.toStringShort
import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity import info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity
import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.CommandQueueProvider import info.nightscout.androidaps.interfaces.CommandQueueProvider
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.IobCobCalculator import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
@ -229,10 +233,6 @@ class ActionsFragment : DaggerFragment() {
.toObservable(EventInitializationChanged::class.java) .toObservable(EventInitializationChanged::class.java)
.observeOn(aapsSchedulers.main) .observeOn(aapsSchedulers.main)
.subscribe({ updateGui() }, fabricPrivacy::logException) .subscribe({ updateGui() }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventRefreshOverview::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ updateGui() }, fabricPrivacy::logException)
disposable += rxBus disposable += rxBus
.toObservable(EventExtendedBolusChange::class.java) .toObservable(EventExtendedBolusChange::class.java)
.observeOn(aapsSchedulers.main) .observeOn(aapsSchedulers.main)

View file

@ -1,32 +1,133 @@
package info.nightscout.androidaps.plugins.general.overview package info.nightscout.androidaps.plugins.general.overview
import com.jjoe64.graphview.series.BarGraphSeries
import com.jjoe64.graphview.series.DataPoint
import com.jjoe64.graphview.series.LineGraphSeries
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.IobTotal
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.entities.TemporaryBasal
import info.nightscout.androidaps.database.entities.TemporaryTarget
import info.nightscout.androidaps.extensions.convertedToPercent import info.nightscout.androidaps.extensions.convertedToPercent
import info.nightscout.androidaps.extensions.toStringFull import info.nightscout.androidaps.extensions.toStringFull
import info.nightscout.androidaps.extensions.toStringShort import info.nightscout.androidaps.extensions.toStringShort
import info.nightscout.androidaps.extensions.valueToUnits
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.Profile import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.FixedLineGraphSeries
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.Scale
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.ScaledDataPoint
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.CobInfo
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
import java.util.*
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
import kotlin.collections.ArrayList
@Singleton @Singleton
class OverviewData @Inject constructor( class OverviewData @Inject constructor(
private val resourceHelper: ResourceHelper, private val resourceHelper: ResourceHelper,
private val dateUtil: DateUtil private val dateUtil: DateUtil,
private val sp: SP,
private val activePlugin: ActivePlugin,
private val defaultValueHelper: DefaultValueHelper,
private val profileFunction: ProfileFunction
) { ) {
enum class Property { enum class Property {
TIME,
CALC_PROGRESS,
PROFILE, PROFILE,
TEMPORARY_BASAL TEMPORARY_BASAL,
EXTENDED_BOLUS,
TEMPORARY_TARGET,
BG,
IOB_COB,
SENSITIVITY,
GRAPH
} }
@get:Synchronized @set:Synchronized var rangeToDisplay = 6 // for graph
var profile: Profile? = null var toTime: Long = 0
var fromTime: Long = 0
var endTime: Long = 0
@get:Synchronized @set:Synchronized fun initRange() {
rangeToDisplay = sp.getInt(R.string.key_rangetodisplay, 6)
val calendar = Calendar.getInstance().also {
it.timeInMillis = System.currentTimeMillis()
it[Calendar.MILLISECOND] = 0
it[Calendar.SECOND] = 0
it[Calendar.MINUTE] = 0
it.add(Calendar.HOUR, 1)
}
toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific
fromTime = toTime - T.hours(rangeToDisplay.toLong()).msecs()
endTime = toTime
}
/*
* PROFILE
*/
var profile: Profile? = null
var profileName: String? = null var profileName: String? = null
var profileNameWithRemainingTime: String? = null
val profileBackgroudColor: Int
get() =
profile?.let { profile ->
if (profile.percentage != 100 || profile.timeshift != 0) resourceHelper.gc(R.color.ribbonWarning)
else resourceHelper.gc(R.color.ribbonDefault)
} ?: resourceHelper.gc(R.color.ribbonTextDefault)
val profileTextColor: Int
get() =
profile?.let { profile ->
if (profile.percentage != 100 || profile.timeshift != 0) resourceHelper.gc(R.color.ribbonTextWarning)
else resourceHelper.gc(R.color.ribbonTextDefault)
} ?: resourceHelper.gc(R.color.ribbonTextDefault)
/*
* CALC PROGRESS
*/
var calcProgress: String = ""
/*
* BG
*/
var lastBg: GlucoseValue? = null
val lastBgColor: Int
get() = lastBg?.let { lastBg ->
when {
lastBg.valueToUnits(profileFunction.getUnits()) < defaultValueHelper.determineLowLine() -> resourceHelper.gc(R.color.low)
lastBg.valueToUnits(profileFunction.getUnits()) > defaultValueHelper.determineHighLine() -> resourceHelper.gc(R.color.high)
else -> resourceHelper.gc(R.color.inrange)
}
} ?: resourceHelper.gc(R.color.inrange)
val isActualBg: Boolean
get() =
lastBg?.let { lastBg ->
lastBg.timestamp > dateUtil.now() - T.mins(9).msecs()
} ?: false
/*
* TEMPORARY BASAL
*/
var temporaryBasal: TemporaryBasal? = null var temporaryBasal: TemporaryBasal? = null
@ -62,4 +163,119 @@ class OverviewData @Inject constructor(
val temporaryBasalColor: Int val temporaryBasalColor: Int
get() = temporaryBasal?.let { resourceHelper.gc(R.color.basal) } get() = temporaryBasal?.let { resourceHelper.gc(R.color.basal) }
?: resourceHelper.gc(R.color.defaulttextcolor) ?: resourceHelper.gc(R.color.defaulttextcolor)
/*
* EXTENDED BOLUS
*/
var extendedBolus: ExtendedBolus? = null
val extendedBolusText: String
get() =
extendedBolus?.let { extendedBolus ->
if (activePlugin.activePump.isFakingTempsByExtendedBoluses) resourceHelper.gs(R.string.pump_basebasalrate, extendedBolus.rate)
else ""
} ?: ""
val extendedBolusDialogText: String
get() = extendedBolus?.toStringFull(dateUtil) ?: ""
/*
* IOB, COB
*/
var bolusIob: IobTotal? = null
var basalIob: IobTotal? = null
var cobInfo: CobInfo? = null
var lastCarbsTime: Long = 0L
val iobText: String
get() =
bolusIob?.let { bolusIob ->
basalIob?.let { basalIob ->
resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob)
} ?: resourceHelper.gs(R.string.value_unavailable_short)
} ?: resourceHelper.gs(R.string.value_unavailable_short)
val iobDialogText: String
get() =
bolusIob?.let { bolusIob ->
basalIob?.let { basalIob ->
resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob) + "\n" +
resourceHelper.gs(R.string.bolus) + ": " + resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob) + "\n" +
resourceHelper.gs(R.string.basal) + ": " + resourceHelper.gs(R.string.formatinsulinunits, basalIob.basaliob)
} ?: resourceHelper.gs(R.string.value_unavailable_short)
} ?: resourceHelper.gs(R.string.value_unavailable_short)
/*
* TEMP TARGET
*/
var temporarytarget: TemporaryTarget? = null
/*
* SENSITIVITY
*/
var lastAutosensData: AutosensData? = null
/*
* Graphs
*/
var bgReadingsArray: List<GlucoseValue> = ArrayList()
var maxBgValue = Double.MIN_VALUE
var bucketedGraphSeries: PointsWithLabelGraphSeries<DataPointWithLabelInterface> = PointsWithLabelGraphSeries()
var bgReadingGraphSeries: PointsWithLabelGraphSeries<DataPointWithLabelInterface> = PointsWithLabelGraphSeries()
var predictionsGraphSeries: PointsWithLabelGraphSeries<DataPointWithLabelInterface> = PointsWithLabelGraphSeries()
var maxBasalValueFound = 0.0
val basalScale = Scale()
var baseBasalGraphSeries: LineGraphSeries<ScaledDataPoint> = LineGraphSeries()
var tempBasalGraphSeries: LineGraphSeries<ScaledDataPoint> = LineGraphSeries()
var basalLineGraphSeries: LineGraphSeries<ScaledDataPoint> = LineGraphSeries()
var absoluteBasalGraphSeries: LineGraphSeries<ScaledDataPoint> = LineGraphSeries()
var temporaryTargetSeries: LineGraphSeries<DataPoint> = LineGraphSeries()
var maxIAValue = 0.0
val actScale = Scale()
var activitySeries: FixedLineGraphSeries<ScaledDataPoint> = FixedLineGraphSeries()
var activityPredictionSeries: FixedLineGraphSeries<ScaledDataPoint> = FixedLineGraphSeries()
var maxTreatmentsValue = 0.0
var treatmentsSeries: PointsWithLabelGraphSeries<DataPointWithLabelInterface> = PointsWithLabelGraphSeries()
var maxIobValueFound = Double.MIN_VALUE
val iobScale = Scale()
var iobSeries: FixedLineGraphSeries<ScaledDataPoint> = FixedLineGraphSeries()
var absIobSeries: FixedLineGraphSeries<ScaledDataPoint> = FixedLineGraphSeries()
var iobPredictions1Series: PointsWithLabelGraphSeries<DataPointWithLabelInterface> = PointsWithLabelGraphSeries()
var iobPredictions2Series: PointsWithLabelGraphSeries<DataPointWithLabelInterface> = PointsWithLabelGraphSeries()
var maxBGIValue = Double.MIN_VALUE
val bgiScale = Scale()
var minusBgiSeries: FixedLineGraphSeries<ScaledDataPoint> = FixedLineGraphSeries()
var minusBgiHistSeries: FixedLineGraphSeries<ScaledDataPoint> = FixedLineGraphSeries()
var maxCobValueFound = Double.MIN_VALUE
val cobScale = Scale()
var cobSeries: FixedLineGraphSeries<ScaledDataPoint> = FixedLineGraphSeries()
var cobMinFailOverSeries: PointsWithLabelGraphSeries<DataPointWithLabelInterface> = PointsWithLabelGraphSeries()
var maxDevValueFound = Double.MIN_VALUE
val devScale = Scale()
var deviationsSeries: BarGraphSeries<OverviewPlugin.DeviationDataPoint> = BarGraphSeries()
var maxRatioValueFound = 5.0 //even if sens data equals 0 for all the period, minimum scale is between 95% and 105%
var minRatioValueFound = -maxRatioValueFound
val ratioScale = Scale()
var ratioSeries: LineGraphSeries<ScaledDataPoint> = LineGraphSeries()
var maxFromMaxValueFound = Double.MIN_VALUE
var maxFromMinValueFound = Double.MIN_VALUE
val dsMaxScale = Scale()
val dsMinScale = Scale()
var dsMaxSeries: LineGraphSeries<ScaledDataPoint> = LineGraphSeries()
var dsMinSeries: LineGraphSeries<ScaledDataPoint> = LineGraphSeries()
} }

View file

@ -20,7 +20,6 @@ import android.widget.LinearLayout
import android.widget.RelativeLayout import android.widget.RelativeLayout
import android.widget.TextView import android.widget.TextView
import androidx.core.text.toSpanned import androidx.core.text.toSpanned
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.jjoe64.graphview.GraphView import com.jjoe64.graphview.GraphView
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
@ -28,15 +27,19 @@ import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.Constants import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.database.entities.TemporaryTarget
import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.database.interfaces.end import info.nightscout.androidaps.database.interfaces.end
import info.nightscout.androidaps.databinding.OverviewFragmentBinding import info.nightscout.androidaps.databinding.OverviewFragmentBinding
import info.nightscout.androidaps.dialogs.* import info.nightscout.androidaps.dialogs.*
import info.nightscout.androidaps.events.* import info.nightscout.androidaps.events.EventAcceptOpenLoopChange
import info.nightscout.androidaps.extensions.* import info.nightscout.androidaps.events.EventInitializationChanged
import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.events.EventPumpStatusChanged
import info.nightscout.androidaps.events.EventRefreshOverview
import info.nightscout.androidaps.extensions.directionToIcon
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.extensions.valueToUnitsString
import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.logging.UserEntryLogger
@ -48,11 +51,9 @@ import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus
import info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity import info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity
import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverview import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverview
import info.nightscout.androidaps.plugins.general.overview.graphData.GraphData import info.nightscout.androidaps.plugins.general.overview.graphData.GraphData
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.GlucoseValueDataPoint
import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationStore import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationStore
import info.nightscout.androidaps.plugins.general.wear.events.EventWearInitiateAction import info.nightscout.androidaps.plugins.general.wear.events.EventWearInitiateAction
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType import info.nightscout.androidaps.plugins.pump.common.defs.PumpType
import info.nightscout.androidaps.plugins.source.DexcomPlugin import info.nightscout.androidaps.plugins.source.DexcomPlugin
import info.nightscout.androidaps.plugins.source.XdripPlugin import info.nightscout.androidaps.plugins.source.XdripPlugin
@ -62,7 +63,6 @@ import info.nightscout.androidaps.skins.SkinProvider
import info.nightscout.androidaps.utils.* import info.nightscout.androidaps.utils.*
import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.buildHelper.BuildHelper import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.utils.extensions.*
import info.nightscout.androidaps.utils.protection.ProtectionCheck import info.nightscout.androidaps.utils.protection.ProtectionCheck
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.rx.AapsSchedulers
@ -70,15 +70,11 @@ import info.nightscout.androidaps.utils.sharedPreferences.SP
import info.nightscout.androidaps.utils.ui.UIRunnable import info.nightscout.androidaps.utils.ui.UIRunnable
import info.nightscout.androidaps.utils.wizard.QuickWizard import info.nightscout.androidaps.utils.wizard.QuickWizard
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import kotlinx.coroutines.Dispatchers import io.reactivex.rxkotlin.plusAssign
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min import kotlin.math.min
class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickListener { class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickListener {
@ -117,6 +113,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
@Inject lateinit var repository: AppRepository @Inject lateinit var repository: AppRepository
@Inject lateinit var glucoseStatusProvider: GlucoseStatusProvider @Inject lateinit var glucoseStatusProvider: GlucoseStatusProvider
@Inject lateinit var overviewData: OverviewData @Inject lateinit var overviewData: OverviewData
@Inject lateinit var overviewPlugin: OverviewPlugin
private val disposable = CompositeDisposable() private val disposable = CompositeDisposable()
@ -124,17 +121,14 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
private var smallHeight = false private var smallHeight = false
private lateinit var dm: DisplayMetrics private lateinit var dm: DisplayMetrics
private var axisWidth: Int = 0 private var axisWidth: Int = 0
private var rangeToDisplay = 6 // for graph
private lateinit var loopHandler: Handler
private var refreshLoop: Runnable? = null private var refreshLoop: Runnable? = null
private lateinit var handler: Handler
private val secondaryGraphs = ArrayList<GraphView>() private val secondaryGraphs = ArrayList<GraphView>()
private val secondaryGraphsLabel = ArrayList<TextView>() private val secondaryGraphsLabel = ArrayList<TextView>()
private var carbAnimation: AnimationDrawable? = null private var carbAnimation: AnimationDrawable? = null
private val graphLock = Object()
private var _binding: OverviewFragmentBinding? = null private var _binding: OverviewFragmentBinding? = null
// This property is only valid between onCreateView and // This property is only valid between onCreateView and
@ -151,7 +145,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
loopHandler = Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper) handler = Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper)
// pre-process landscape mode // pre-process landscape mode
val screenWidth = dm.widthPixels val screenWidth = dm.widthPixels
@ -177,13 +171,14 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
carbAnimation?.setEnterFadeDuration(1200) carbAnimation?.setEnterFadeDuration(1200)
carbAnimation?.setExitFadeDuration(1200) carbAnimation?.setExitFadeDuration(1200)
rangeToDisplay = sp.getInt(R.string.key_rangetodisplay, 6)
binding.graphsLayout.bgGraph.setOnLongClickListener { binding.graphsLayout.bgGraph.setOnLongClickListener {
rangeToDisplay += 6 overviewData.rangeToDisplay += 6
rangeToDisplay = if (rangeToDisplay > 24) 6 else rangeToDisplay overviewData.rangeToDisplay = if (overviewData.rangeToDisplay > 24) 6 else overviewData.rangeToDisplay
sp.putInt(R.string.key_rangetodisplay, rangeToDisplay) sp.putInt(R.string.key_rangetodisplay, overviewData.rangeToDisplay)
updateGUI("rangeChange") overviewData.initRange()
updateGUI("rangeChange", OverviewData.Property.GRAPH)
rxBus.send(EventPreferenceChange(resourceHelper, R.string.key_rangetodisplay))
sp.putBoolean(R.string.key_objectiveusescale, true) sp.putBoolean(R.string.key_objectiveusescale, true)
false false
} }
@ -212,51 +207,32 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
disposable.clear() disposable.clear()
loopHandler.removeCallbacksAndMessages(null) handler.removeCallbacksAndMessages(null)
} }
@Synchronized @Synchronized
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
disposable.add(rxBus disposable += activePlugin.activeOverview.overviewBus
.toObservable(EventRefreshOverview::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
if (it.now) updateGUI(it.from)
else scheduleUpdateGUI(it.from)
}, fabricPrivacy::logException))
disposable.add(rxBus
.toObservable(EventUpdateOverview::class.java) .toObservable(EventUpdateOverview::class.java)
.observeOn(aapsSchedulers.main) .observeOn(aapsSchedulers.main)
.subscribe({ updateGUI(it.what) }, fabricPrivacy::logException)) .subscribe({ updateGUI(it.from, it.what) }, fabricPrivacy::logException)
disposable.add(rxBus disposable.add(rxBus
.toObservable(EventExtendedBolusChange::class.java) .toObservable(EventRefreshOverview::class.java)
.observeOn(aapsSchedulers.io) .observeOn(aapsSchedulers.io)
.subscribe({ scheduleUpdateGUI("EventExtendedBolusChange") }, fabricPrivacy::logException)) .subscribe({
disposable.add(rxBus if (it.now) overviewPlugin.refreshLoop(it.from)
.toObservable(EventTreatmentChange::class.java) else scheduleUpdateGUI(it.from)
.observeOn(aapsSchedulers.io) }, fabricPrivacy::logException))
.subscribe({ scheduleUpdateGUI("EventTreatmentChange") }, fabricPrivacy::logException))
disposable.add(rxBus
.toObservable(EventTempTargetChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ scheduleUpdateGUI("EventTempTargetChange") }, fabricPrivacy::logException))
disposable.add(rxBus disposable.add(rxBus
.toObservable(EventAcceptOpenLoopChange::class.java) .toObservable(EventAcceptOpenLoopChange::class.java)
.observeOn(aapsSchedulers.io) .observeOn(aapsSchedulers.io)
.subscribe({ scheduleUpdateGUI("EventAcceptOpenLoopChange") }, fabricPrivacy::logException)) .subscribe({ scheduleUpdateGUI("EventAcceptOpenLoopChange") }, fabricPrivacy::logException))
disposable.add(rxBus
.toObservable(EventTherapyEventChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ scheduleUpdateGUI("EventCareportalEventChange") }, fabricPrivacy::logException))
disposable.add(rxBus disposable.add(rxBus
.toObservable(EventInitializationChanged::class.java) .toObservable(EventInitializationChanged::class.java)
.observeOn(aapsSchedulers.io) .observeOn(aapsSchedulers.main)
.subscribe({ scheduleUpdateGUI("EventInitializationChanged") }, fabricPrivacy::logException)) .subscribe({ updateGUI("EventInitializationChanged", OverviewData.Property.TIME) }, fabricPrivacy::logException))
disposable.add(rxBus
.toObservable(EventAutosensCalculationFinished::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ scheduleUpdateGUI("EventAutosensCalculationFinished") }, fabricPrivacy::logException))
disposable.add(rxBus disposable.add(rxBus
.toObservable(EventPreferenceChange::class.java) .toObservable(EventPreferenceChange::class.java)
.observeOn(aapsSchedulers.io) .observeOn(aapsSchedulers.io)
@ -269,18 +245,14 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
.toObservable(EventPumpStatusChanged::class.java) .toObservable(EventPumpStatusChanged::class.java)
.observeOn(aapsSchedulers.main) .observeOn(aapsSchedulers.main)
.subscribe({ updatePumpStatus(it) }, fabricPrivacy::logException)) .subscribe({ updatePumpStatus(it) }, fabricPrivacy::logException))
disposable.add(rxBus
.toObservable(EventIobCalculationProgress::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ binding.graphsLayout.iobCalculationProgress.text = it.progress }, fabricPrivacy::logException))
refreshLoop = Runnable { refreshLoop = Runnable {
scheduleUpdateGUI("refreshLoop") overviewPlugin.refreshLoop("refreshLoop")
loopHandler.postDelayed(refreshLoop, 60 * 1000L) handler.postDelayed(refreshLoop, 60 * 1000L)
} }
loopHandler.postDelayed(refreshLoop, 60 * 1000L) handler.postDelayed(refreshLoop, 60 * 1000L)
updateGUI("onResume") for (p in OverviewData.Property.values()) updateGUI("onResume", p)
} }
@Synchronized @Synchronized
@ -340,7 +312,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
R.id.accept_temp_button -> { R.id.accept_temp_button -> {
profileFunction.getProfile() ?: return profileFunction.getProfile() ?: return
if (loopPlugin.isEnabled(PluginType.LOOP)) { if (loopPlugin.isEnabled(PluginType.LOOP)) {
loopHandler.post { handler.post {
val lastRun = loopPlugin.lastRun val lastRun = loopPlugin.lastRun
loopPlugin.invoke("Accept temp button", false) loopPlugin.invoke("Accept temp button", false)
if (lastRun?.lastAPSRun != null && lastRun.constraintsProcessed?.isChangeRequested == true) { if (lastRun?.lastAPSRun != null && lastRun.constraintsProcessed?.isChangeRequested == true) {
@ -490,149 +462,11 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
} }
private fun prepareGraphsIfNeeded(numOfGraphs: Int) { private fun processAps() {
synchronized(graphLock) {
if (numOfGraphs != secondaryGraphs.size - 1) {
//aapsLogger.debug("New secondary graph count ${numOfGraphs-1}")
// rebuild needed
secondaryGraphs.clear()
secondaryGraphsLabel.clear()
binding.graphsLayout.iobGraph.removeAllViews()
for (i in 1 until numOfGraphs) {
val relativeLayout = RelativeLayout(context)
relativeLayout.layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
val graph = GraphView(context)
graph.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, resourceHelper.dpToPx(skinProvider.activeSkin().secondaryGraphHeight)).also { it.setMargins(0, resourceHelper.dpToPx(15), 0, resourceHelper.dpToPx(10)) }
graph.gridLabelRenderer?.gridColor = resourceHelper.gc(R.color.graphgrid)
graph.gridLabelRenderer?.reloadStyles()
graph.gridLabelRenderer?.isHorizontalLabelsVisible = false
graph.gridLabelRenderer?.labelVerticalWidth = axisWidth
graph.gridLabelRenderer?.numVerticalLabels = 3
graph.viewport.backgroundColor = Color.argb(20, 255, 255, 255) // 8% of gray
relativeLayout.addView(graph)
val label = TextView(context)
val layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).also { it.setMargins(resourceHelper.dpToPx(30), resourceHelper.dpToPx(25), 0, 0) }
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP)
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT)
label.layoutParams = layoutParams
relativeLayout.addView(label)
secondaryGraphsLabel.add(label)
binding.graphsLayout.iobGraph.addView(relativeLayout)
secondaryGraphs.add(graph)
}
}
}
}
var task: Runnable? = null
private fun scheduleUpdateGUI(from: String) {
class UpdateRunnable : Runnable {
override fun run() {
updateGUI(from)
task = null
}
}
view?.removeCallbacks(task)
task = UpdateRunnable()
view?.postDelayed(task, 500)
}
fun updateGUI(what: OverviewData.Property) {
when (what) {
OverviewData.Property.PROFILE -> TODO()
OverviewData.Property.TEMPORARY_BASAL -> {
// Basal, TBR
binding.infoLayout.baseBasal.text = overviewData.temporaryBasalText
binding.infoLayout.baseBasal.setTextColor(overviewData.temporaryBasalColor)
binding.infoLayout.baseBasalIcon.setImageResource(overviewData.temporaryBasalIcon)
binding.infoLayout.basalLayout.setOnClickListener {
activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.basal), overviewData.temporaryBasalDialogText) }
}
}
}
}
@SuppressLint("SetTextI18n")
fun updateGUI(from: String) {
if (_binding == null) return
aapsLogger.debug("UpdateGUI from $from")
binding.infoLayout.time.text = dateUtil.timeString(dateUtil.now())
if (overviewData.profile == null) {
binding.loopPumpStatusLayout.pumpStatus.setText(R.string.noprofileset)
binding.loopPumpStatusLayout.pumpStatusLayout.visibility = View.VISIBLE
binding.loopPumpStatusLayout.loopLayout.visibility = View.GONE
return
}
binding.notifications.let { notificationStore.updateNotifications(it) }
binding.loopPumpStatusLayout.pumpStatusLayout.visibility = View.GONE
binding.loopPumpStatusLayout.loopLayout.visibility = View.VISIBLE
val profile = overviewData.profile ?: return
val actualBG = iobCobCalculator.ads.actualBg()
val lastBG = iobCobCalculator.ads.lastBg()
val pump = activePlugin.activePump val pump = activePlugin.activePump
val units = profileFunction.getUnits()
val lowLine = defaultValueHelper.determineLowLine()
val highLine = defaultValueHelper.determineHighLine()
val lastRun = loopPlugin.lastRun
val predictionsAvailable = if (config.APS) lastRun?.request?.hasPredictions == true else config.NSCLIENT
try {
updateGraph(lastRun, predictionsAvailable, lowLine, highLine, pump, profile)
} catch (e: IllegalStateException) {
return // view no longer exists
}
//Start with updating the BG as it is unaffected by loop.
// **** BG value ****
if (lastBG != null) {
val color = when {
lastBG.valueToUnits(units) < lowLine -> resourceHelper.gc(R.color.low)
lastBG.valueToUnits(units) > highLine -> resourceHelper.gc(R.color.high)
else -> resourceHelper.gc(R.color.inrange)
}
binding.infoLayout.bg.text = lastBG.valueToUnitsString(units)
binding.infoLayout.bg.setTextColor(color)
binding.infoLayout.arrow.setImageResource(trendCalculator.getTrendArrow(lastBG).directionToIcon())
binding.infoLayout.arrow.setColorFilter(color)
val glucoseStatus = glucoseStatusProvider.glucoseStatusData
if (glucoseStatus != null) {
binding.infoLayout.deltaLarge.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units)
binding.infoLayout.deltaLarge.setTextColor(color)
binding.infoLayout.delta.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units)
binding.infoLayout.avgDelta.text = Profile.toSignedUnitsString(glucoseStatus.shortAvgDelta, glucoseStatus.shortAvgDelta * Constants.MGDL_TO_MMOLL, units)
binding.infoLayout.longAvgDelta.text = Profile.toSignedUnitsString(glucoseStatus.longAvgDelta, glucoseStatus.longAvgDelta * Constants.MGDL_TO_MMOLL, units)
} else {
binding.infoLayout.delta.text = "Δ " + resourceHelper.gs(R.string.notavailable)
binding.infoLayout.avgDelta.text = ""
binding.infoLayout.longAvgDelta.text = ""
}
// strike through if BG is old
binding.infoLayout.bg.let { overview_bg ->
var flag = overview_bg.paintFlags
flag = if (actualBG == null) {
flag or Paint.STRIKE_THRU_TEXT_FLAG
} else flag and Paint.STRIKE_THRU_TEXT_FLAG.inv()
overview_bg.paintFlags = flag
}
binding.infoLayout.timeAgo.text = dateUtil.minAgo(resourceHelper, lastBG.timestamp)
binding.infoLayout.timeAgoShort.text = "(" + dateUtil.minAgoShort(lastBG.timestamp) + ")"
}
val closedLoopEnabled = constraintChecker.isClosedLoopAllowed()
// aps mode // aps mode
val closedLoopEnabled = constraintChecker.isClosedLoopAllowed()
if (config.APS && pump.pumpDescription.isTempBasalCapable) { if (config.APS && pump.pumpDescription.isTempBasalCapable) {
binding.infoLayout.apsMode.visibility = View.VISIBLE binding.infoLayout.apsMode.visibility = View.VISIBLE
binding.infoLayout.timeLayout.visibility = View.GONE binding.infoLayout.timeLayout.visibility = View.GONE
@ -693,15 +527,193 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
binding.infoLayout.timeLayout.visibility = View.VISIBLE binding.infoLayout.timeLayout.visibility = View.VISIBLE
} }
// pump status from ns
binding.pump.text = nsDeviceStatus.pumpStatus
binding.pump.setOnClickListener { activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.pump), nsDeviceStatus.extendedPumpStatus) } }
// OpenAPS status from ns
binding.openaps.text = nsDeviceStatus.openApsStatus
binding.openaps.setOnClickListener { activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.openaps), nsDeviceStatus.extendedOpenApsStatus) } }
// Uploader status from ns
binding.uploader.text = nsDeviceStatus.uploaderStatusSpanned
binding.uploader.setOnClickListener { activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.uploader), nsDeviceStatus.extendedUploaderStatus) } }
}
private fun prepareGraphsIfNeeded(numOfGraphs: Int) {
if (numOfGraphs != secondaryGraphs.size - 1) {
//aapsLogger.debug("New secondary graph count ${numOfGraphs-1}")
// rebuild needed
secondaryGraphs.clear()
secondaryGraphsLabel.clear()
binding.graphsLayout.iobGraph.removeAllViews()
for (i in 1 until numOfGraphs) {
val relativeLayout = RelativeLayout(context)
relativeLayout.layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
val graph = GraphView(context)
graph.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, resourceHelper.dpToPx(skinProvider.activeSkin().secondaryGraphHeight)).also { it.setMargins(0, resourceHelper.dpToPx(15), 0, resourceHelper.dpToPx(10)) }
graph.gridLabelRenderer?.gridColor = resourceHelper.gc(R.color.graphgrid)
graph.gridLabelRenderer?.reloadStyles()
graph.gridLabelRenderer?.isHorizontalLabelsVisible = false
graph.gridLabelRenderer?.labelVerticalWidth = axisWidth
graph.gridLabelRenderer?.numVerticalLabels = 3
graph.viewport.backgroundColor = Color.argb(20, 255, 255, 255) // 8% of gray
relativeLayout.addView(graph)
val label = TextView(context)
val layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).also { it.setMargins(resourceHelper.dpToPx(30), resourceHelper.dpToPx(25), 0, 0) }
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP)
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT)
label.layoutParams = layoutParams
relativeLayout.addView(label)
secondaryGraphsLabel.add(label)
binding.graphsLayout.iobGraph.addView(relativeLayout)
secondaryGraphs.add(graph)
}
}
}
var task: Runnable? = null
private fun scheduleUpdateGUI(from: String) {
class UpdateRunnable : Runnable {
override fun run() {
overviewPlugin.refreshLoop(from)
task = null
}
}
handler.removeCallbacks(task)
task = UpdateRunnable()
handler.postDelayed(task, 500)
}
@Suppress("UNUSED_PARAMETER")
@SuppressLint("SetTextI18n")
fun updateGUI(from: String, what: OverviewData.Property) {
// if (what != OverviewData.Property.CALC_PROGRESS)
// aapsLogger.debug(LTag.UI, "UpdateGui $from $what")
if (overviewData.profile == null) {
binding.loopPumpStatusLayout.pumpStatus.setText(R.string.noprofileset)
binding.loopPumpStatusLayout.pumpStatusLayout.visibility = View.VISIBLE
binding.loopPumpStatusLayout.loopLayout.visibility = View.GONE
return
}
binding.notifications.let { notificationStore.updateNotifications(it) }
binding.loopPumpStatusLayout.pumpStatusLayout.visibility = View.GONE
binding.loopPumpStatusLayout.loopLayout.visibility = View.VISIBLE
val units = profileFunction.getUnits()
val pump = activePlugin.activePump
when (what) {
OverviewData.Property.BG -> {
binding.infoLayout.bg.text = overviewData.lastBg?.valueToUnitsString(units)
?: resourceHelper.gs(R.string.notavailable)
binding.infoLayout.bg.setTextColor(overviewData.lastBgColor)
binding.infoLayout.arrow.setImageResource(trendCalculator.getTrendArrow(overviewData.lastBg).directionToIcon())
binding.infoLayout.arrow.setColorFilter(overviewData.lastBgColor)
val glucoseStatus = glucoseStatusProvider.glucoseStatusData
if (glucoseStatus != null) {
binding.infoLayout.deltaLarge.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units)
binding.infoLayout.deltaLarge.setTextColor(overviewData.lastBgColor)
binding.infoLayout.delta.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units)
binding.infoLayout.avgDelta.text = Profile.toSignedUnitsString(glucoseStatus.shortAvgDelta, glucoseStatus.shortAvgDelta * Constants.MGDL_TO_MMOLL, units)
binding.infoLayout.longAvgDelta.text = Profile.toSignedUnitsString(glucoseStatus.longAvgDelta, glucoseStatus.longAvgDelta * Constants.MGDL_TO_MMOLL, units)
} else {
binding.infoLayout.deltaLarge.text = "Δ " + resourceHelper.gs(R.string.notavailable)
binding.infoLayout.delta.text = "Δ " + resourceHelper.gs(R.string.notavailable)
binding.infoLayout.avgDelta.text = ""
binding.infoLayout.longAvgDelta.text = ""
}
// strike through if BG is old
binding.infoLayout.bg.paintFlags =
if (!overviewData.isActualBg) binding.infoLayout.bg.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
else binding.infoLayout.bg.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
binding.infoLayout.timeAgo.text = dateUtil.minAgo(resourceHelper, overviewData.lastBg?.timestamp)
binding.infoLayout.timeAgoShort.text = "(" + dateUtil.minAgoShort(overviewData.lastBg?.timestamp) + ")"
}
OverviewData.Property.PROFILE -> {
binding.loopPumpStatusLayout.activeProfile.text = overviewData.profileNameWithRemainingTime
?: ""
binding.loopPumpStatusLayout.activeProfile.setBackgroundColor(overviewData.profileBackgroudColor)
binding.loopPumpStatusLayout.activeProfile.setTextColor(overviewData.profileTextColor)
}
OverviewData.Property.TEMPORARY_BASAL -> {
binding.infoLayout.baseBasal.text = overviewData.temporaryBasalText
binding.infoLayout.baseBasal.setTextColor(overviewData.temporaryBasalColor)
binding.infoLayout.baseBasalIcon.setImageResource(overviewData.temporaryBasalIcon)
binding.infoLayout.basalLayout.setOnClickListener {
activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.basal), overviewData.temporaryBasalDialogText) }
}
}
OverviewData.Property.EXTENDED_BOLUS -> {
binding.infoLayout.extendedBolus.text = overviewData.extendedBolusText
binding.infoLayout.extendedBolus.setOnClickListener {
activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.extended_bolus), overviewData.extendedBolusDialogText) }
}
binding.infoLayout.extendedLayout.visibility = (overviewData.extendedBolus != null && !pump.isFakingTempsByExtendedBoluses).toVisibility()
}
OverviewData.Property.TIME -> {
binding.infoLayout.time.text = dateUtil.timeString(dateUtil.now())
// Status lights
binding.statusLightsLayout.statusLights.visibility = (sp.getBoolean(R.string.key_show_statuslights, true) || config.NSCLIENT).toVisibility()
statusLightHandler.updateStatusLights(binding.statusLightsLayout.cannulaAge, binding.statusLightsLayout.insulinAge, binding.statusLightsLayout.reservoirLevel, binding.statusLightsLayout.sensorAge, null, binding.statusLightsLayout.pbAge, binding.statusLightsLayout.batteryLevel)
processButtonsVisibility()
processAps()
}
OverviewData.Property.IOB_COB -> {
binding.infoLayout.iob.text = overviewData.iobText
binding.infoLayout.iobLayout.setOnClickListener {
activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.iob), overviewData.iobDialogText) }
}
// cob
var cobText: String = resourceHelper.gs(R.string.value_unavailable_short)
overviewData.cobInfo?.let { cobInfo ->
if (cobInfo.displayCob != null) {
cobText = resourceHelper.gs(R.string.format_carbs, cobInfo.displayCob!!.toInt())
if (cobInfo.futureCarbs > 0) cobText += "(" + DecimalFormatter.to0Decimal(cobInfo.futureCarbs) + ")"
}
}
binding.infoLayout.cob.text = cobText
val constraintsProcessed = loopPlugin.lastRun?.constraintsProcessed
val lastRun = loopPlugin.lastRun
if (config.APS && constraintsProcessed != null && lastRun != null) {
if (constraintsProcessed.carbsReq > 0) {
//only display carbsreq when carbs have not been entered recently
if (overviewData.lastCarbsTime < lastRun.lastAPSRun) {
cobText = cobText + " | " + constraintsProcessed.carbsReq + " " + resourceHelper.gs(R.string.required)
}
if (carbAnimation?.isRunning == false)
carbAnimation?.start()
} else {
carbAnimation?.stop()
carbAnimation?.selectDrawable(0)
}
}
}
OverviewData.Property.TEMPORARY_TARGET -> {
// temp target // temp target
val tempTarget: ValueWrapper<TemporaryTarget> = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() val tempTarget = overviewData.temporarytarget
if (tempTarget is ValueWrapper.Existing) { if (tempTarget != null) {
binding.loopPumpStatusLayout.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning)) binding.loopPumpStatusLayout.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning))
binding.loopPumpStatusLayout.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.ribbonWarning)) binding.loopPumpStatusLayout.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.ribbonWarning))
binding.loopPumpStatusLayout.tempTarget.text = Profile.toTargetRangeString(tempTarget.value.lowTarget, tempTarget.value.highTarget, GlucoseUnit.MGDL, units) + " " + dateUtil.untilString(tempTarget.value.end, resourceHelper) binding.loopPumpStatusLayout.tempTarget.text = Profile.toTargetRangeString(tempTarget.lowTarget, tempTarget.highTarget, GlucoseUnit.MGDL, units) + " " + dateUtil.untilString(tempTarget.end, resourceHelper)
} else { } else {
// If the target is not the same as set in the profile then oref has overridden it // If the target is not the same as set in the profile then oref has overridden it
val targetUsed = lastRun?.constraintsProcessed?.targetBG ?: 0.0 overviewData.profile?.let { profile ->
val targetUsed = loopPlugin.lastRun?.constraintsProcessed?.targetBG ?: 0.0
if (targetUsed != 0.0 && abs(profile.getTargetMgdl() - targetUsed) > 0.01) { if (targetUsed != 0.0 && abs(profile.getTargetMgdl() - targetUsed) > 0.01) {
aapsLogger.debug("Adjusted target. Profile: ${profile.getTargetMgdl()} APS: $targetUsed") aapsLogger.debug("Adjusted target. Profile: ${profile.getTargetMgdl()} APS: $targetUsed")
@ -714,176 +726,36 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
binding.loopPumpStatusLayout.tempTarget.text = Profile.toTargetRangeString(profile.getTargetLowMgdl(), profile.getTargetHighMgdl(), GlucoseUnit.MGDL, units) binding.loopPumpStatusLayout.tempTarget.text = Profile.toTargetRangeString(profile.getTargetLowMgdl(), profile.getTargetHighMgdl(), GlucoseUnit.MGDL, units)
} }
} }
// Extended bolus
val extendedBolus = repository.getExtendedBolusActiveAt(dateUtil.now()).blockingGet()
binding.infoLayout.extendedBolus.text =
if (extendedBolus is ValueWrapper.Existing && !pump.isFakingTempsByExtendedBoluses)
resourceHelper.gs(R.string.pump_basebasalrate, extendedBolus.value.rate)
else ""
binding.infoLayout.extendedBolus.setOnClickListener {
if (extendedBolus is ValueWrapper.Existing) activity?.let {
OKDialog.show(it, resourceHelper.gs(R.string.extended_bolus), extendedBolus.value.toStringFull(dateUtil))
}
}
binding.infoLayout.extendedLayout.visibility = (extendedBolus is ValueWrapper.Existing && !pump.isFakingTempsByExtendedBoluses).toVisibility()
// Active profile
binding.loopPumpStatusLayout.activeProfile.text = profileFunction.getProfileNameWithRemainingTime()
if (profile.percentage != 100 || profile.timeshift != 0) {
binding.loopPumpStatusLayout.activeProfile.setBackgroundColor(resourceHelper.gc(R.color.ribbonWarning))
binding.loopPumpStatusLayout.activeProfile.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning))
} else {
binding.loopPumpStatusLayout.activeProfile.setBackgroundColor(resourceHelper.gc(R.color.ribbonDefault))
binding.loopPumpStatusLayout.activeProfile.setTextColor(resourceHelper.gc(R.color.ribbonTextDefault))
}
processButtonsVisibility()
// iob
val bolusIob = iobCobCalculator.calculateIobFromBolus().round()
val basalIob = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round()
binding.infoLayout.iob.text = resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob)
binding.infoLayout.iobLayout.setOnClickListener {
activity?.let {
OKDialog.show(it, resourceHelper.gs(R.string.iob),
resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob) + "\n" +
resourceHelper.gs(R.string.bolus) + ": " + resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob) + "\n" +
resourceHelper.gs(R.string.basal) + ": " + resourceHelper.gs(R.string.formatinsulinunits, basalIob.basaliob)
)
} }
} }
// Status lights OverviewData.Property.GRAPH -> {
binding.statusLightsLayout.statusLights.visibility = (sp.getBoolean(R.string.key_show_statuslights, true) || config.NSCLIENT).toVisibility() val graphData = GraphData(injector, binding.graphsLayout.bgGraph)
statusLightHandler.updateStatusLights(binding.statusLightsLayout.cannulaAge, binding.statusLightsLayout.insulinAge, binding.statusLightsLayout.reservoirLevel, binding.statusLightsLayout.sensorAge, null, binding.statusLightsLayout.pbAge, binding.statusLightsLayout.batteryLevel)
// cob
var cobText: String = resourceHelper.gs(R.string.value_unavailable_short)
val cobInfo = iobCobCalculator.getCobInfo(false, "Overview COB")
if (cobInfo.displayCob != null) {
cobText = resourceHelper.gs(R.string.format_carbs, cobInfo.displayCob!!.toInt())
if (cobInfo.futureCarbs > 0) cobText += "(" + DecimalFormatter.to0Decimal(cobInfo.futureCarbs) + ")"
}
if (config.APS && lastRun?.constraintsProcessed != null) {
if (lastRun.constraintsProcessed!!.carbsReq > 0) {
//only display carbsreq when carbs have not been entered recently
val lastCarb = repository.getLastCarbsRecordWrapped().blockingGet()
val lastCarbsTime = if (lastCarb is ValueWrapper.Existing) lastCarb.value.timestamp else 0L
if (lastCarbsTime < lastRun.lastAPSRun) {
cobText = cobText + " | " + lastRun.constraintsProcessed!!.carbsReq + " " + resourceHelper.gs(R.string.required)
}
binding.infoLayout.cob.text = cobText
if (carbAnimation?.isRunning == false)
carbAnimation?.start()
} else {
binding.infoLayout.cob.text = cobText
carbAnimation?.stop()
carbAnimation?.selectDrawable(0)
}
} else binding.infoLayout.cob.text = cobText
// pump status from ns
binding.pump.text = nsDeviceStatus.pumpStatus
binding.pump.setOnClickListener { activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.pump), nsDeviceStatus.extendedPumpStatus) } }
// OpenAPS status from ns
binding.openaps.text = nsDeviceStatus.openApsStatus
binding.openaps.setOnClickListener { activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.openaps), nsDeviceStatus.extendedOpenApsStatus) } }
// Uploader status from ns
binding.uploader.text = nsDeviceStatus.uploaderStatusSpanned
binding.uploader.setOnClickListener { activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.uploader), nsDeviceStatus.extendedUploaderStatus) } }
// Sensitivity
if (sp.getBoolean(R.string.key_openapsama_useautosens, false) && constraintChecker.isAutosensModeEnabled().value()) {
binding.infoLayout.sensitivityIcon.setImageResource(R.drawable.ic_swap_vert_black_48dp_green)
} else {
binding.infoLayout.sensitivityIcon.setImageResource(R.drawable.ic_x_swap_vert)
}
binding.infoLayout.sensitivity.text =
iobCobCalculator.ads.getLastAutosensData("Overview", aapsLogger, dateUtil)?.let { autosensData ->
String.format(Locale.ENGLISH, "%.0f%%", autosensData.autosensResult.ratio * 100)
} ?: ""
}
private fun updateGraph(lastRun: LoopInterface.LastRun?, predictionsAvailable: Boolean, lowLine: Double, highLine: Double, pump: Pump, profile: Profile) {
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
if (_binding == null) return@launch
val menuChartSettings = overviewMenus.setting val menuChartSettings = overviewMenus.setting
prepareGraphsIfNeeded(menuChartSettings.size) graphData.addInRangeArea(overviewData.fromTime, overviewData.endTime, defaultValueHelper.determineLowLine(), defaultValueHelper.determineHighLine())
val graphData = GraphData(injector, binding.graphsLayout.bgGraph, iobCobCalculator) graphData.addBgReadings(menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal])
val secondaryGraphsData: ArrayList<GraphData> = ArrayList() if (buildHelper.isDev()) graphData.addBucketedData()
graphData.addTreatments()
// do preparation in different thread if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal])
withContext(Dispatchers.Default) { graphData.addActivity(0.8)
// align to hours if (pump.pumpDescription.isTempBasalCapable && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal])
val calendar = Calendar.getInstance() graphData.addBasals()
calendar.timeInMillis = System.currentTimeMillis() graphData.addTargetLine()
calendar[Calendar.MILLISECOND] = 0 graphData.addNowLine(dateUtil.now())
calendar[Calendar.SECOND] = 0
calendar[Calendar.MINUTE] = 0
calendar.add(Calendar.HOUR, 1)
val hoursToFetch: Int
val toTime: Long
val fromTime: Long
val endTime: Long
val apsResult = if (config.APS) lastRun?.constraintsProcessed else nsDeviceStatus.getAPSResult(injector)
if (predictionsAvailable && apsResult != null && menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal]) {
var predictionHours = (ceil(apsResult.latestPredictionsTime - System.currentTimeMillis().toDouble()) / (60 * 60 * 1000)).toInt()
predictionHours = min(2, predictionHours)
predictionHours = max(0, predictionHours)
hoursToFetch = rangeToDisplay - predictionHours
toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific
fromTime = toTime - T.hours(hoursToFetch.toLong()).msecs()
endTime = toTime + T.hours(predictionHours.toLong()).msecs()
} else {
hoursToFetch = rangeToDisplay
toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific
fromTime = toTime - T.hours(hoursToFetch.toLong()).msecs()
endTime = toTime
}
val now = System.currentTimeMillis()
// ------------------ 1st graph
// **** In range Area ****
graphData.addInRangeArea(fromTime, endTime, lowLine, highLine)
// **** BG ****
if (predictionsAvailable && menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal])
graphData.addBgReadings(fromTime, toTime, highLine, apsResult?.predictions?.map { bg -> GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, resourceHelper) }?.toMutableList())
else graphData.addBgReadings(fromTime, toTime, highLine, null)
if (buildHelper.isDev()) graphData.addBucketedData(fromTime, toTime)
// Treatments
graphData.addTreatments(fromTime, endTime)
// set manual x bounds to have nice steps // set manual x bounds to have nice steps
graphData.setNumVerticalLabels() graphData.setNumVerticalLabels()
graphData.formatAxis(fromTime, endTime) graphData.formatAxis(overviewData.fromTime, overviewData.endTime)
graphData.performUpdate()
if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal]) // 2nd graphs
graphData.addActivity(fromTime, endTime, false, 0.8) prepareGraphsIfNeeded(menuChartSettings.size)
val secondaryGraphsData: ArrayList<GraphData> = ArrayList()
// add basal data val now = System.currentTimeMillis()
if (pump.pumpDescription.isTempBasalCapable && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal])
graphData.addBasals(fromTime, now, lowLine / graphData.maxY / 1.2)
// add target line
graphData.addTargetLine(fromTime, toTime, profile, loopPlugin.lastRun)
// **** NOW line ****
graphData.addNowLine(now)
// ------------------ 2nd graph
synchronized(graphLock) {
for (g in 0 until min(secondaryGraphs.size, menuChartSettings.size + 1)) { for (g in 0 until min(secondaryGraphs.size, menuChartSettings.size + 1)) {
val secondGraphData = GraphData(injector, secondaryGraphs[g], iobCobCalculator) val secondGraphData = GraphData(injector, secondaryGraphs[g])
var useABSForScale = false var useABSForScale = false
var useIobForScale = false var useIobForScale = false
var useCobForScale = false var useCobForScale = false
@ -900,27 +772,21 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] -> useRatioForScale = true menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] -> useRatioForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] -> useDSForScale = true menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] -> useDSForScale = true
} }
val alignIobScale = menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]
val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]
if (menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal]) secondGraphData.addAbsIob(fromTime, now, useABSForScale, 1.0) if (menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal]) secondGraphData.addAbsIob(useABSForScale, 1.0)
if (menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]) secondGraphData.addIob(fromTime, now, useIobForScale, 1.0, menuChartSettings[g + 1][OverviewMenus.CharType.PRE.ordinal], alignIobScale) if (menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]) secondGraphData.addIob(useIobForScale, 1.0)
if (menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal]) secondGraphData.addCob(fromTime, now, useCobForScale, if (useCobForScale) 1.0 else 0.5) if (menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal]) secondGraphData.addCob(useCobForScale, if (useCobForScale) 1.0 else 0.5)
if (menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal]) secondGraphData.addDeviations(fromTime, now, useDevForScale, 1.0, alignDevBgiScale) if (menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal]) secondGraphData.addDeviations(useDevForScale, 1.0)
if (menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]) secondGraphData.addMinusBGI(fromTime, endTime, useBGIForScale, if (alignDevBgiScale) 1.0 else 0.8, alignDevBgiScale) if (menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]) secondGraphData.addMinusBGI(useBGIForScale, if (alignDevBgiScale) 1.0 else 0.8)
if (menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal]) secondGraphData.addRatio(fromTime, now, useRatioForScale, if (useRatioForScale) 1.0 else 0.8) if (menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal]) secondGraphData.addRatio(useRatioForScale, if (useRatioForScale) 1.0 else 0.8)
if (menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] && buildHelper.isDev()) secondGraphData.addDeviationSlope(fromTime, now, useDSForScale, 1.0) if (menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] && buildHelper.isDev()) secondGraphData.addDeviationSlope(useDSForScale, 1.0)
// set manual x bounds to have nice steps // set manual x bounds to have nice steps
secondGraphData.formatAxis(fromTime, endTime) secondGraphData.formatAxis(overviewData.fromTime, overviewData.endTime)
secondGraphData.addNowLine(now) secondGraphData.addNowLine(now)
secondaryGraphsData.add(secondGraphData) secondaryGraphsData.add(secondGraphData)
} }
}
}
// finally enforce drawing of graphs in UI thread
graphData.performUpdate()
synchronized(graphLock) {
for (g in 0 until min(secondaryGraphs.size, menuChartSettings.size + 1)) { for (g in 0 until min(secondaryGraphs.size, menuChartSettings.size + 1)) {
secondaryGraphsLabel[g].text = overviewMenus.enabledTypes(g + 1) secondaryGraphsLabel[g].text = overviewMenus.enabledTypes(g + 1)
secondaryGraphs[g].visibility = ( secondaryGraphs[g].visibility = (
@ -935,6 +801,23 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
secondaryGraphsData[g].performUpdate() secondaryGraphsData[g].performUpdate()
} }
} }
OverviewData.Property.CALC_PROGRESS -> {
binding.graphsLayout.iobCalculationProgress.text = overviewData.calcProgress
}
OverviewData.Property.SENSITIVITY -> {
if (sp.getBoolean(R.string.key_openapsama_useautosens, false) && constraintChecker.isAutosensModeEnabled().value()) {
binding.infoLayout.sensitivityIcon.setImageResource(R.drawable.ic_swap_vert_black_48dp_green)
} else {
binding.infoLayout.sensitivityIcon.setImageResource(R.drawable.ic_x_swap_vert)
}
binding.infoLayout.sensitivity.text =
overviewData.lastAutosensData?.let { autosensData ->
String.format(Locale.ENGLISH, "%.0f%%", autosensData.autosensResult.ratio * 100)
} ?: ""
}
} }
} }
} }

View file

@ -46,6 +46,7 @@ class OverviewMenus @Inject constructor(
} }
companion object { companion object {
const val MAX_GRAPHS = 5 // including main const val MAX_GRAPHS = 5 // including main
} }
@ -58,8 +59,6 @@ class OverviewMenus @Inject constructor(
return r.toString() return r.toString()
} }
private var _setting: MutableList<Array<Boolean>> = ArrayList() private var _setting: MutableList<Array<Boolean>> = ArrayList()
val setting: List<Array<Boolean>> val setting: List<Array<Boolean>>
@ -71,7 +70,7 @@ class OverviewMenus @Inject constructor(
aapsLogger.debug(sts) aapsLogger.debug(sts)
} }
private fun loadGraphConfig() { fun loadGraphConfig() {
val sts = sp.getString(R.string.key_graphconfig, "") val sts = sp.getString(R.string.key_graphconfig, "")
if (sts.isNotEmpty()) { if (sts.isNotEmpty()) {
_setting = Gson().fromJson(sts, Array<Array<Boolean>>::class.java).toMutableList() _setting = Gson().fromJson(sts, Array<Array<Boolean>>::class.java).toMutableList()
@ -88,7 +87,6 @@ class OverviewMenus @Inject constructor(
} }
fun setupChartMenu(chartButton: ImageButton) { fun setupChartMenu(chartButton: ImageButton) {
loadGraphConfig()
val settingsCopy = setting val settingsCopy = setting
val numOfGraphs = settingsCopy.size // 1 main + x secondary val numOfGraphs = settingsCopy.size // 1 main + x secondary
@ -100,6 +98,8 @@ class OverviewMenus @Inject constructor(
} }
val popup = PopupMenu(v.context, v) val popup = PopupMenu(v.context, v)
val used = arrayListOf<Int>()
for (g in 0 until numOfGraphs) { for (g in 0 until numOfGraphs) {
if (g != 0 && g < numOfGraphs) { if (g != 0 && g < numOfGraphs) {
val dividerItem = popup.menu.add(Menu.NONE, g, Menu.NONE, "------- ${resourceHelper.gs(R.string.graph_menu_divider_header)} $g -------") val dividerItem = popup.menu.add(Menu.NONE, g, Menu.NONE, "------- ${resourceHelper.gs(R.string.graph_menu_divider_header)} $g -------")
@ -112,6 +112,7 @@ class OverviewMenus @Inject constructor(
var insert = true var insert = true
if (m == CharType.PRE) insert = predictionsAvailable if (m == CharType.PRE) insert = predictionsAvailable
if (m == CharType.DEVSLOPE) insert = buildHelper.isDev() if (m == CharType.DEVSLOPE) insert = buildHelper.isDev()
if (used.contains(m.ordinal)) insert = false
if (insert) { if (insert) {
val item = popup.menu.add(Menu.NONE, m.ordinal + 100 * (g + 1), Menu.NONE, resourceHelper.gs(m.nameId)) val item = popup.menu.add(Menu.NONE, m.ordinal + 100 * (g + 1), Menu.NONE, resourceHelper.gs(m.nameId))
val title = item.title val title = item.title
@ -120,6 +121,7 @@ class OverviewMenus @Inject constructor(
item.title = s item.title = s
item.isCheckable = true item.isCheckable = true
item.isChecked = settingsCopy[g][m.ordinal] item.isChecked = settingsCopy[g][m.ordinal]
if (settingsCopy[g][m.ordinal]) used.add(m.ordinal)
} }
} }
} }
@ -131,17 +133,21 @@ class OverviewMenus @Inject constructor(
popup.setOnMenuItemClickListener { popup.setOnMenuItemClickListener {
// id < 100 graph header - divider 1, 2, 3 ..... // id < 100 graph header - divider 1, 2, 3 .....
if (it.itemId == numOfGraphs) { when {
it.itemId == numOfGraphs -> {
// add new empty // add new empty
_setting.add(Array(CharType.values().size) { false }) _setting.add(Array(CharType.values().size) { false })
} else if (it.itemId < 100) { }
it.itemId < 100 -> {
// remove graph // remove graph
_setting.removeAt(it.itemId) _setting.removeAt(it.itemId)
} else { }
else -> {
val graphNumber = it.itemId / 100 - 1 val graphNumber = it.itemId / 100 - 1
val item = it.itemId % 100 val item = it.itemId % 100
_setting[graphNumber][item] = !it.isChecked _setting[graphNumber][item] = !it.isChecked
} }
}
storeGraphConfig() storeGraphConfig()
setupChartMenu(chartButton) setupChartMenu(chartButton)
rxBus.send(EventRefreshOverview("OnMenuItemClickListener", now = true)) rxBus.send(EventRefreshOverview("OnMenuItemClickListener", now = true))
@ -153,4 +159,11 @@ class OverviewMenus @Inject constructor(
} }
} }
fun isEnabledIn(type: CharType): Int {
val settingsCopy = setting
val numOfGraphs = settingsCopy.size // 1 main + x secondary
for (g in 0 until numOfGraphs) if (settingsCopy[g][type.ordinal]) return g
return -1
}
} }

View file

@ -1,31 +1,50 @@
package info.nightscout.androidaps.plugins.general.overview package info.nightscout.androidaps.plugins.general.overview
import android.graphics.DashPathEffect
import android.graphics.Paint
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference import androidx.preference.SwitchPreference
import com.jjoe64.graphview.series.BarGraphSeries
import com.jjoe64.graphview.series.DataPoint
import com.jjoe64.graphview.series.LineGraphSeries
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.events.EventAppInitialized import info.nightscout.androidaps.data.IobTotal
import info.nightscout.androidaps.events.EventProfileSwitchChanged import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.events.EventRefreshOverview import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.events.EventTempBasalChange import info.nightscout.androidaps.database.entities.Bolus
import info.nightscout.androidaps.events.*
import info.nightscout.androidaps.extensions.* import info.nightscout.androidaps.extensions.*
import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.aps.events.EventLoopInvoked
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults
import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus
import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverview import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverview
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.*
import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationStore import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationStore
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult
import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventBucketedDataCreated
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress
import info.nightscout.androidaps.utils.*
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.sharedPreferences.SP import info.nightscout.androidaps.utils.sharedPreferences.SP
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.plusAssign
import org.json.JSONObject import org.json.JSONObject
import java.util.*
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
import kotlin.math.abs
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
@Singleton @Singleton
class OverviewPlugin @Inject constructor( class OverviewPlugin @Inject constructor(
@ -39,9 +58,17 @@ class OverviewPlugin @Inject constructor(
resourceHelper: ResourceHelper, resourceHelper: ResourceHelper,
private val config: Config, private val config: Config,
private val dateUtil: DateUtil, private val dateUtil: DateUtil,
private val translator: Translator,
// private val profiler: Profiler,
private val profileFunction: ProfileFunction, private val profileFunction: ProfileFunction,
private val iobCobCalculator: IobCobCalculator, private val iobCobCalculator: IobCobCalculator,
private val overviewData: OverviewData private val repository: AppRepository,
private val defaultValueHelper: DefaultValueHelper,
private val loopPlugin: LoopPlugin,
private val activePlugin: ActivePlugin,
private val nsDeviceStatus: NSDeviceStatus,
private val overviewData: OverviewData,
private val overviewMenus: OverviewMenus
) : PluginBase(PluginDescription() ) : PluginBase(PluginDescription()
.mainType(PluginType.GENERAL) .mainType(PluginType.GENERAL)
.fragmentClass(OverviewFragment::class.qualifiedName) .fragmentClass(OverviewFragment::class.qualifiedName)
@ -57,8 +84,15 @@ class OverviewPlugin @Inject constructor(
private var disposable: CompositeDisposable = CompositeDisposable() private var disposable: CompositeDisposable = CompositeDisposable()
override val overviewBus = RxBusWrapper(aapsSchedulers)
class DeviationDataPoint(x: Double, y: Double, var color: Int, scale: Scale) : ScaledDataPoint(x, y, scale)
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
overviewMenus.loadGraphConfig()
overviewData.initRange()
notificationStore.createNotificationChannel() notificationStore.createNotificationChannel()
disposable += rxBus disposable += rxBus
.toObservable(EventNewNotification::class.java) .toObservable(EventNewNotification::class.java)
@ -75,17 +109,62 @@ class OverviewPlugin @Inject constructor(
rxBus.send(EventRefreshOverview("EventDismissNotification")) rxBus.send(EventRefreshOverview("EventDismissNotification"))
}, fabricPrivacy::logException) }, fabricPrivacy::logException)
disposable += rxBus disposable += rxBus
.toObservable(EventAppInitialized::class.java) .toObservable(EventIobCalculationProgress::class.java)
.observeOn(aapsSchedulers.io) .observeOn(aapsSchedulers.main)
.subscribe( { loadAll() }, fabricPrivacy::logException) .subscribe({ overviewData.calcProgress = it.progress; overviewBus.send(EventUpdateOverview("EventIobCalculationProgress", OverviewData.Property.CALC_PROGRESS)) }, fabricPrivacy::logException)
disposable += rxBus disposable += rxBus
.toObservable(EventTempBasalChange::class.java) .toObservable(EventTempBasalChange::class.java)
.observeOn(aapsSchedulers.io) .observeOn(aapsSchedulers.io)
.subscribe( { loadTemporaryBasal() }, fabricPrivacy::logException) .subscribe({ loadTemporaryBasal("EventTempBasalChange") }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventExtendedBolusChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ loadExtendedBolus("EventExtendedBolusChange") }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventNewBG::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ loadBg("EventNewBG") }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventTempTargetChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ loadTemporaryTarget("EventTempTargetChange") }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventTreatmentChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({
loadIobCobResults("EventTreatmentChange")
prepareTreatmentsData("EventTreatmentChange")
overviewBus.send(EventUpdateOverview("EventTreatmentChange", OverviewData.Property.GRAPH))
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventTherapyEventChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({
prepareTreatmentsData("EventTherapyEventChange")
overviewBus.send(EventUpdateOverview("EventTherapyEventChange", OverviewData.Property.GRAPH))
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventBucketedDataCreated::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({
prepareBucketedData("EventBucketedDataCreated")
prepareBgData("EventBucketedDataCreated")
overviewBus.send(EventUpdateOverview("EventBucketedDataCreated", OverviewData.Property.GRAPH))
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventLoopInvoked::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ preparePredictions("EventLoopInvoked") }, fabricPrivacy::logException)
disposable.add(rxBus disposable.add(rxBus
.toObservable(EventProfileSwitchChanged::class.java) .toObservable(EventProfileSwitchChanged::class.java)
.observeOn(aapsSchedulers.io) .observeOn(aapsSchedulers.io)
.subscribe({ loadProfile() }, fabricPrivacy::logException)) .subscribe({ loadProfile("EventProfileSwitchChanged") }, fabricPrivacy::logException))
disposable.add(rxBus
.toObservable(EventAutosensCalculationFinished::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ refreshLoop("EventAutosensCalculationFinished") }, fabricPrivacy::logException))
Thread { loadAll("onResume") }.start()
} }
override fun onStop() { override fun onStop() {
@ -160,21 +239,600 @@ class OverviewPlugin @Inject constructor(
.storeDouble(R.string.key_statuslights_bat_critical, sp, resourceHelper) .storeDouble(R.string.key_statuslights_bat_critical, sp, resourceHelper)
} }
private fun loadAll() { @Volatile var runningRefresh = false
loadProfile() override fun refreshLoop(from: String) {
loadTemporaryBasal() if (runningRefresh) return
runningRefresh = true
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.BG))
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.TIME))
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.TEMPORARY_BASAL))
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.EXTENDED_BOLUS))
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.IOB_COB))
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.TEMPORARY_TARGET))
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.SENSITIVITY))
loadAsData(from)
preparePredictions(from)
prepareBasalData(from)
prepareTemporaryTargetData(from)
prepareTreatmentsData(from)
prepareIobAutosensData(from)
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.GRAPH))
aapsLogger.debug(LTag.UI, "refreshLoop finished")
runningRefresh = false
} }
@Synchronized @Suppress("SameParameterValue")
private fun loadProfile() { private fun loadAll(from: String) {
loadBg(from)
loadProfile(from)
loadTemporaryBasal(from)
loadExtendedBolus(from)
loadTemporaryTarget(from)
loadIobCobResults(from)
loadAsData(from)
prepareBasalData(from)
prepareTemporaryTargetData(from)
prepareTreatmentsData(from)
// prepareIobAutosensData(from)
// preparePredictions(from)
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.GRAPH))
aapsLogger.debug(LTag.UI, "loadAll finished")
}
private fun loadProfile(from: String) {
overviewData.profile = profileFunction.getProfile() overviewData.profile = profileFunction.getProfile()
overviewData.profileName = profileFunction.getProfileName() overviewData.profileName = profileFunction.getProfileName()
rxBus.send(EventUpdateOverview(OverviewData.Property.PROFILE)) overviewData.profileNameWithRemainingTime = profileFunction.getProfileNameWithRemainingTime()
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.PROFILE))
}
private fun loadTemporaryBasal(from: String) {
overviewData.temporaryBasal = iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now())
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.TEMPORARY_BASAL))
}
private fun loadExtendedBolus(from: String) {
overviewData.extendedBolus = iobCobCalculator.getExtendedBolus(dateUtil.now())
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.EXTENDED_BOLUS))
}
private fun loadTemporaryTarget(from: String) {
val tempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet()
if (tempTarget is ValueWrapper.Existing) overviewData.temporarytarget = tempTarget.value
else overviewData.temporarytarget = null
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.TEMPORARY_TARGET))
}
private fun loadAsData(from: String) {
overviewData.lastAutosensData = iobCobCalculator.ads.getLastAutosensData("Overview", aapsLogger, dateUtil)
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.SENSITIVITY))
}
private fun loadBg(from: String) {
val gvWrapped = repository.getLastGlucoseValueWrapped().blockingGet()
if (gvWrapped is ValueWrapper.Existing) overviewData.lastBg = gvWrapped.value
else overviewData.lastBg = null
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.BG))
}
private fun loadIobCobResults(from: String) {
overviewData.bolusIob = iobCobCalculator.calculateIobFromBolus().round()
overviewData.basalIob = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round()
overviewData.cobInfo = iobCobCalculator.getCobInfo(false, "Overview COB")
val lastCarbs = repository.getLastCarbsRecordWrapped().blockingGet()
overviewData.lastCarbsTime = if (lastCarbs is ValueWrapper.Existing) lastCarbs.value.timestamp else 0L
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.IOB_COB))
} }
@Synchronized @Synchronized
private fun loadTemporaryBasal() { @Suppress("SameParameterValue", "UNUSED_PARAMETER")
overviewData.temporaryBasal = iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now()) private fun prepareBgData(from: String) {
rxBus.send(EventUpdateOverview(OverviewData.Property.TEMPORARY_BASAL)) // val start = dateUtil.now()
var maxBgValue = Double.MIN_VALUE
overviewData.bgReadingsArray = repository.compatGetBgReadingsDataFromTime(overviewData.fromTime, overviewData.toTime, false).blockingGet()
val bgListArray: MutableList<DataPointWithLabelInterface> = ArrayList()
for (bg in overviewData.bgReadingsArray) {
if (bg.timestamp < overviewData.fromTime || bg.timestamp > overviewData.toTime) continue
if (bg.value > maxBgValue) maxBgValue = bg.value
bgListArray.add(GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, resourceHelper))
}
overviewData.bgReadingGraphSeries = PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] })
overviewData.maxBgValue = Profile.fromMgdlToUnits(maxBgValue, profileFunction.getUnits())
if (defaultValueHelper.determineHighLine() > maxBgValue) overviewData.maxBgValue = defaultValueHelper.determineHighLine()
overviewData.maxBgValue = addUpperChartMargin(overviewData.maxBgValue)
// profiler.log(LTag.UI, "prepareBgData() $from", start)
}
@Suppress("UNUSED_PARAMETER")
@Synchronized
private fun preparePredictions(from: String) {
// val start = dateUtil.now()
val apsResult = if (config.APS) loopPlugin.lastRun?.constraintsProcessed else nsDeviceStatus.getAPSResult(injector)
val predictionsAvailable = if (config.APS) loopPlugin.lastRun?.request?.hasPredictions == true else config.NSCLIENT
val menuChartSettings = overviewMenus.setting
// align to hours
val calendar = Calendar.getInstance().also {
it.timeInMillis = System.currentTimeMillis()
it[Calendar.MILLISECOND] = 0
it[Calendar.SECOND] = 0
it[Calendar.MINUTE] = 0
it.add(Calendar.HOUR, 1)
}
if (predictionsAvailable && apsResult != null && menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal]) {
var predictionHours = (ceil(apsResult.latestPredictionsTime - System.currentTimeMillis().toDouble()) / (60 * 60 * 1000)).toInt()
predictionHours = min(2, predictionHours)
predictionHours = max(0, predictionHours)
val hoursToFetch = overviewData.rangeToDisplay - predictionHours
overviewData.toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific
overviewData.fromTime = overviewData.toTime - T.hours(hoursToFetch.toLong()).msecs()
overviewData.endTime = overviewData.toTime + T.hours(predictionHours.toLong()).msecs()
} else {
overviewData.toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific
overviewData.fromTime = overviewData.toTime - T.hours(overviewData.rangeToDisplay.toLong()).msecs()
overviewData.endTime = overviewData.toTime
}
val bgListArray: MutableList<DataPointWithLabelInterface> = ArrayList()
val predictions: MutableList<GlucoseValueDataPoint>? = apsResult?.predictions
?.map { bg -> GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, resourceHelper) }
?.toMutableList()
if (predictions != null) {
predictions.sortWith { o1: GlucoseValueDataPoint, o2: GlucoseValueDataPoint -> o1.x.compareTo(o2.x) }
for (prediction in predictions) if (prediction.data.value >= 40) bgListArray.add(prediction)
}
overviewData.predictionsGraphSeries = PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] })
// profiler.log(LTag.UI, "preparePredictions() $from", start)
}
@Synchronized
@Suppress("SameParameterValue", "UNUSED_PARAMETER")
private fun prepareBucketedData(from: String) {
// val start = dateUtil.now()
val bucketedData = iobCobCalculator.ads.getBucketedDataTableCopy() ?: return
if (bucketedData.isEmpty()) {
aapsLogger.debug("No bucketed data.")
return
}
val bucketedListArray: MutableList<DataPointWithLabelInterface> = ArrayList()
for (inMemoryGlucoseValue in bucketedData) {
if (inMemoryGlucoseValue.timestamp < overviewData.fromTime || inMemoryGlucoseValue.timestamp > overviewData.toTime) continue
bucketedListArray.add(InMemoryGlucoseValueDataPoint(inMemoryGlucoseValue, profileFunction, resourceHelper))
}
overviewData.bucketedGraphSeries = PointsWithLabelGraphSeries(Array(bucketedListArray.size) { i -> bucketedListArray[i] })
// profiler.log(LTag.UI, "prepareBucketedData() $from", start)
}
@Suppress("UNUSED_PARAMETER")
@Synchronized
private fun prepareBasalData(from: String) {
// val start = dateUtil.now()
overviewData.maxBasalValueFound = 0.0
val baseBasalArray: MutableList<ScaledDataPoint> = ArrayList()
val tempBasalArray: MutableList<ScaledDataPoint> = ArrayList()
val basalLineArray: MutableList<ScaledDataPoint> = ArrayList()
val absoluteBasalLineArray: MutableList<ScaledDataPoint> = ArrayList()
var lastLineBasal = 0.0
var lastAbsoluteLineBasal = -1.0
var lastBaseBasal = 0.0
var lastTempBasal = 0.0
var time = overviewData.fromTime
while (time < overviewData.toTime) {
val profile = profileFunction.getProfile(time)
if (profile == null) {
time += 60 * 1000L
continue
}
val basalData = iobCobCalculator.getBasalData(profile, time)
val baseBasalValue = basalData.basal
var absoluteLineValue = baseBasalValue
var tempBasalValue = 0.0
var basal = 0.0
if (basalData.isTempBasalRunning) {
tempBasalValue = basalData.tempBasalAbsolute
absoluteLineValue = tempBasalValue
if (tempBasalValue != lastTempBasal) {
tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, overviewData.basalScale))
tempBasalArray.add(ScaledDataPoint(time, tempBasalValue.also { basal = it }, overviewData.basalScale))
}
if (lastBaseBasal != 0.0) {
baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, overviewData.basalScale))
baseBasalArray.add(ScaledDataPoint(time, 0.0, overviewData.basalScale))
lastBaseBasal = 0.0
}
} else {
if (baseBasalValue != lastBaseBasal) {
baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, overviewData.basalScale))
baseBasalArray.add(ScaledDataPoint(time, baseBasalValue.also { basal = it }, overviewData.basalScale))
lastBaseBasal = baseBasalValue
}
if (lastTempBasal != 0.0) {
tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, overviewData.basalScale))
tempBasalArray.add(ScaledDataPoint(time, 0.0, overviewData.basalScale))
} }
} }
if (baseBasalValue != lastLineBasal) {
basalLineArray.add(ScaledDataPoint(time, lastLineBasal, overviewData.basalScale))
basalLineArray.add(ScaledDataPoint(time, baseBasalValue, overviewData.basalScale))
}
if (absoluteLineValue != lastAbsoluteLineBasal) {
absoluteBasalLineArray.add(ScaledDataPoint(time, lastAbsoluteLineBasal, overviewData.basalScale))
absoluteBasalLineArray.add(ScaledDataPoint(time, basal, overviewData.basalScale))
}
lastAbsoluteLineBasal = absoluteLineValue
lastLineBasal = baseBasalValue
lastTempBasal = tempBasalValue
overviewData.maxBasalValueFound = max(overviewData.maxBasalValueFound, max(tempBasalValue, baseBasalValue))
time += 60 * 1000L
}
// final points
basalLineArray.add(ScaledDataPoint(overviewData.toTime, lastLineBasal, overviewData.basalScale))
baseBasalArray.add(ScaledDataPoint(overviewData.toTime, lastBaseBasal, overviewData.basalScale))
tempBasalArray.add(ScaledDataPoint(overviewData.toTime, lastTempBasal, overviewData.basalScale))
absoluteBasalLineArray.add(ScaledDataPoint(overviewData.toTime, lastAbsoluteLineBasal, overviewData.basalScale))
// create series
overviewData.baseBasalGraphSeries = LineGraphSeries(Array(baseBasalArray.size) { i -> baseBasalArray[i] }).also {
it.isDrawBackground = true
it.backgroundColor = resourceHelper.gc(R.color.basebasal)
it.thickness = 0
}
overviewData.tempBasalGraphSeries = LineGraphSeries(Array(tempBasalArray.size) { i -> tempBasalArray[i] }).also {
it.isDrawBackground = true
it.backgroundColor = resourceHelper.gc(R.color.tempbasal)
it.thickness = 0
}
overviewData.basalLineGraphSeries = LineGraphSeries(Array(basalLineArray.size) { i -> basalLineArray[i] }).also {
it.setCustomPaint(Paint().also { paint ->
paint.style = Paint.Style.STROKE
paint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2
paint.pathEffect = DashPathEffect(floatArrayOf(2f, 4f), 0f)
paint.color = resourceHelper.gc(R.color.basal)
})
}
overviewData.absoluteBasalGraphSeries = LineGraphSeries(Array(absoluteBasalLineArray.size) { i -> absoluteBasalLineArray[i] }).also {
it.setCustomPaint(Paint().also { absolutePaint ->
absolutePaint.style = Paint.Style.STROKE
absolutePaint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2
absolutePaint.color = resourceHelper.gc(R.color.basal)
})
}
// profiler.log(LTag.UI, "prepareBasalData() $from", start)
}
@Suppress("UNUSED_PARAMETER")
@Synchronized
private fun prepareTemporaryTargetData(from: String) {
// val start = dateUtil.now()
val profile = overviewData.profile ?: return
val units = profileFunction.getUnits()
var toTime = overviewData.toTime
val targetsSeriesArray: MutableList<DataPoint> = ArrayList()
var lastTarget = -1.0
loopPlugin.lastRun?.constraintsProcessed?.let { toTime = max(it.latestPredictionsTime, toTime) }
var time = overviewData.fromTime
while (time < toTime) {
val tt = repository.getTemporaryTargetActiveAt(time).blockingGet()
val value: Double = if (tt is ValueWrapper.Existing) {
Profile.fromMgdlToUnits(tt.value.target(), units)
} else {
Profile.fromMgdlToUnits((profile.getTargetLowMgdl(time) + profile.getTargetHighMgdl(time)) / 2, units)
}
if (lastTarget != value) {
if (lastTarget != -1.0) targetsSeriesArray.add(DataPoint(time.toDouble(), lastTarget))
targetsSeriesArray.add(DataPoint(time.toDouble(), value))
}
lastTarget = value
time += 5 * 60 * 1000L
}
// final point
targetsSeriesArray.add(DataPoint(toTime.toDouble(), lastTarget))
// create series
overviewData.temporaryTargetSeries = LineGraphSeries(Array(targetsSeriesArray.size) { i -> targetsSeriesArray[i] }).also {
it.isDrawBackground = false
it.color = resourceHelper.gc(R.color.tempTargetBackground)
it.thickness = 2
}
// profiler.log(LTag.UI, "prepareTemporaryTargetData() $from", start)
}
@Suppress("UNUSED_PARAMETER")
@Synchronized
private fun prepareTreatmentsData(from: String) {
// val start = dateUtil.now()
overviewData.maxTreatmentsValue = 0.0
val filteredTreatments: MutableList<DataPointWithLabelInterface> = ArrayList()
repository.getBolusesIncludingInvalidFromTimeToTime(overviewData.fromTime, overviewData.endTime, true).blockingGet()
.map { BolusDataPoint(it, resourceHelper, activePlugin, defaultValueHelper) }
.filter { it.data.type != Bolus.Type.SMB || it.data.isValid }
.forEach {
it.y = getNearestBg(it.x.toLong())
filteredTreatments.add(it)
}
repository.getCarbsIncludingInvalidFromTimeToTimeExpanded(overviewData.fromTime, overviewData.endTime, true).blockingGet()
.map { CarbsDataPoint(it, resourceHelper) }
.forEach {
it.y = getNearestBg(it.x.toLong())
filteredTreatments.add(it)
}
// ProfileSwitch
repository.getEffectiveProfileSwitchDataFromTimeToTime(overviewData.fromTime, overviewData.endTime, true).blockingGet()
.map { EffectiveProfileSwitchDataPoint(it) }
.forEach(filteredTreatments::add)
// Extended bolus
if (!activePlugin.activePump.isFakingTempsByExtendedBoluses) {
repository.getExtendedBolusDataFromTimeToTime(overviewData.fromTime, overviewData.endTime, true).blockingGet()
.map { ExtendedBolusDataPoint(it) }
.filter { it.duration != 0L }
.forEach {
it.y = getNearestBg(it.x.toLong())
filteredTreatments.add(it)
}
}
// Careportal
repository.compatGetTherapyEventDataFromToTime(overviewData.fromTime - T.hours(6).msecs(), overviewData.endTime).blockingGet()
.map { TherapyEventDataPoint(it, resourceHelper, profileFunction, translator) }
.filterTimeframe(overviewData.fromTime, overviewData.endTime)
.forEach {
if (it.y == 0.0) it.y = getNearestBg(it.x.toLong())
filteredTreatments.add(it)
}
// increase maxY if a treatment forces it's own height that's higher than a BG value
filteredTreatments.map { it.y }
.maxOrNull()
?.let(::addUpperChartMargin)
?.let { overviewData.maxTreatmentsValue = maxOf(overviewData.maxTreatmentsValue, it) }
overviewData.treatmentsSeries = PointsWithLabelGraphSeries(filteredTreatments.toTypedArray())
// profiler.log(LTag.UI, "prepareTreatmentsData() $from", start)
}
@Suppress("UNUSED_PARAMETER")
@Synchronized
private fun prepareIobAutosensData(from: String) {
// val start = dateUtil.now()
val iobArray: MutableList<ScaledDataPoint> = ArrayList()
val absIobArray: MutableList<ScaledDataPoint> = ArrayList()
overviewData.maxIobValueFound = Double.MIN_VALUE
var lastIob = 0.0
var absLastIob = 0.0
var time = overviewData.fromTime
val minFailOverActiveList: MutableList<DataPointWithLabelInterface> = ArrayList()
val cobArray: MutableList<ScaledDataPoint> = ArrayList()
overviewData.maxCobValueFound = Double.MIN_VALUE
var lastCob = 0
val actArrayHist: MutableList<ScaledDataPoint> = ArrayList()
val actArrayPrediction: MutableList<ScaledDataPoint> = ArrayList()
val now = dateUtil.now().toDouble()
overviewData.maxIAValue = 0.0
val bgiArrayHist: MutableList<ScaledDataPoint> = ArrayList()
val bgiArrayPrediction: MutableList<ScaledDataPoint> = ArrayList()
overviewData.maxBGIValue = Double.MIN_VALUE
val devArray: MutableList<DeviationDataPoint> = ArrayList()
overviewData.maxDevValueFound = Double.MIN_VALUE
val ratioArray: MutableList<ScaledDataPoint> = ArrayList()
overviewData.maxRatioValueFound = 5.0 //even if sens data equals 0 for all the period, minimum scale is between 95% and 105%
overviewData.minRatioValueFound = -5.0
val dsMaxArray: MutableList<ScaledDataPoint> = ArrayList()
val dsMinArray: MutableList<ScaledDataPoint> = ArrayList()
overviewData.maxFromMaxValueFound = Double.MIN_VALUE
overviewData.maxFromMinValueFound = Double.MIN_VALUE
val adsData = iobCobCalculator.ads.clone()
while (time <= overviewData.toTime) {
val profile = profileFunction.getProfile(time)
if (profile == null) {
time += 5 * 60 * 1000L
continue
}
// IOB
val iob = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile)
val baseBasalIob = iobCobCalculator.calculateAbsoluteIobFromBaseBasals(time)
val absIob = IobTotal.combine(iob, baseBasalIob)
val autosensData = adsData.getAutosensDataAtTime(time)
if (abs(lastIob - iob.iob) > 0.02) {
if (abs(lastIob - iob.iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, overviewData.iobScale))
iobArray.add(ScaledDataPoint(time, iob.iob, overviewData.iobScale))
overviewData.maxIobValueFound = maxOf(overviewData.maxIobValueFound, abs(iob.iob))
lastIob = iob.iob
}
if (abs(absLastIob - absIob.iob) > 0.02) {
if (abs(absLastIob - absIob.iob) > 0.2) absIobArray.add(ScaledDataPoint(time, absLastIob, overviewData.iobScale))
absIobArray.add(ScaledDataPoint(time, absIob.iob, overviewData.iobScale))
overviewData.maxIobValueFound = maxOf(overviewData.maxIobValueFound, abs(absIob.iob))
absLastIob = absIob.iob
}
// COB
if (autosensData != null) {
val cob = autosensData.cob.toInt()
if (cob != lastCob) {
if (autosensData.carbsFromBolus > 0) cobArray.add(ScaledDataPoint(time, lastCob.toDouble(), overviewData.cobScale))
cobArray.add(ScaledDataPoint(time, cob.toDouble(), overviewData.cobScale))
overviewData.maxCobValueFound = max(overviewData.maxCobValueFound, cob.toDouble())
lastCob = cob
}
if (autosensData.failoverToMinAbsorbtionRate) {
autosensData.setScale(overviewData.cobScale)
autosensData.setChartTime(time)
minFailOverActiveList.add(autosensData)
}
}
// ACTIVITY
if (time <= now) actArrayHist.add(ScaledDataPoint(time, iob.activity, overviewData.actScale))
else actArrayPrediction.add(ScaledDataPoint(time, iob.activity, overviewData.actScale))
overviewData.maxIAValue = max(overviewData.maxIAValue, abs(iob.activity))
// BGI
val devBgiScale = overviewMenus.isEnabledIn(OverviewMenus.CharType.DEV) == overviewMenus.isEnabledIn(OverviewMenus.CharType.BGI)
val deviation = if (devBgiScale) autosensData?.deviation ?: 0.0 else 0.0
val bgi: Double = iob.activity * profile.getIsfMgdl(time) * 5.0
if (time <= now) bgiArrayHist.add(ScaledDataPoint(time, bgi, overviewData.bgiScale))
else bgiArrayPrediction.add(ScaledDataPoint(time, bgi, overviewData.bgiScale))
overviewData.maxBGIValue = max(overviewData.maxBGIValue, max(abs(bgi), deviation))
// DEVIATIONS
if (autosensData != null) {
var color = resourceHelper.gc(R.color.deviationblack) // "="
if (autosensData.type == "" || autosensData.type == "non-meal") {
if (autosensData.pastSensitivity == "C") color = resourceHelper.gc(R.color.deviationgrey)
if (autosensData.pastSensitivity == "+") color = resourceHelper.gc(R.color.deviationgreen)
if (autosensData.pastSensitivity == "-") color = resourceHelper.gc(R.color.deviationred)
} else if (autosensData.type == "uam") {
color = resourceHelper.gc(R.color.uam)
} else if (autosensData.type == "csf") {
color = resourceHelper.gc(R.color.deviationgrey)
}
devArray.add(DeviationDataPoint(time.toDouble(), autosensData.deviation, color, overviewData.devScale))
overviewData.maxDevValueFound = maxOf(overviewData.maxDevValueFound, abs(autosensData.deviation), abs(bgi))
}
// RATIO
if (autosensData != null) {
ratioArray.add(ScaledDataPoint(time, 100.0 * (autosensData.autosensResult.ratio - 1), overviewData.ratioScale))
overviewData.maxRatioValueFound = max(overviewData.maxRatioValueFound, 100.0 * (autosensData.autosensResult.ratio - 1))
overviewData.minRatioValueFound = min(overviewData.minRatioValueFound, 100.0 * (autosensData.autosensResult.ratio - 1))
}
// DEV SLOPE
if (autosensData != null) {
dsMaxArray.add(ScaledDataPoint(time, autosensData.slopeFromMaxDeviation, overviewData.dsMaxScale))
dsMinArray.add(ScaledDataPoint(time, autosensData.slopeFromMinDeviation, overviewData.dsMinScale))
overviewData.maxFromMaxValueFound = max(overviewData.maxFromMaxValueFound, abs(autosensData.slopeFromMaxDeviation))
overviewData.maxFromMinValueFound = max(overviewData.maxFromMinValueFound, abs(autosensData.slopeFromMinDeviation))
}
time += 5 * 60 * 1000L
}
// IOB
overviewData.iobSeries = FixedLineGraphSeries(Array(iobArray.size) { i -> iobArray[i] }).also {
it.isDrawBackground = true
it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.iob) //50%
it.color = resourceHelper.gc(R.color.iob)
it.thickness = 3
}
overviewData.absIobSeries = FixedLineGraphSeries(Array(absIobArray.size) { i -> absIobArray[i] }).also {
it.isDrawBackground = true
it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.iob) //50%
it.color = resourceHelper.gc(R.color.iob)
it.thickness = 3
}
if (overviewMenus.setting[0][OverviewMenus.CharType.PRE.ordinal]) {
val autosensData = adsData.getLastAutosensData("GraphData", aapsLogger, dateUtil)
val lastAutosensResult = autosensData?.autosensResult ?: AutosensResult()
val isTempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() is ValueWrapper.Existing
val iobPrediction: MutableList<DataPointWithLabelInterface> = ArrayList()
val iobPredictionArray = iobCobCalculator.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget)
for (i in iobPredictionArray) {
iobPrediction.add(i.setColor(resourceHelper.gc(R.color.iobPredAS)))
overviewData.maxIobValueFound = max(overviewData.maxIobValueFound, abs(i.iob))
}
overviewData.iobPredictions1Series = PointsWithLabelGraphSeries(Array(iobPrediction.size) { i -> iobPrediction[i] })
val iobPrediction2: MutableList<DataPointWithLabelInterface> = ArrayList()
val iobPredictionArray2 = iobCobCalculator.calculateIobArrayForSMB(AutosensResult(), SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget)
for (i in iobPredictionArray2) {
iobPrediction2.add(i.setColor(resourceHelper.gc(R.color.iobPred)))
overviewData.maxIobValueFound = max(overviewData.maxIobValueFound, abs(i.iob))
}
overviewData.iobPredictions2Series = PointsWithLabelGraphSeries(Array(iobPrediction2.size) { i -> iobPrediction2[i] })
aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + iobCobCalculator.iobArrayToString(iobPredictionArray))
aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(1.0) + ": " + iobCobCalculator.iobArrayToString(iobPredictionArray2))
} else {
overviewData.iobPredictions1Series = PointsWithLabelGraphSeries()
overviewData.iobPredictions2Series = PointsWithLabelGraphSeries()
}
// COB
overviewData.cobSeries = FixedLineGraphSeries(Array(cobArray.size) { i -> cobArray[i] }).also {
it.isDrawBackground = true
it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.cob) //50%
it.color = resourceHelper.gc(R.color.cob)
it.thickness = 3
}
overviewData.cobMinFailOverSeries = PointsWithLabelGraphSeries(Array(minFailOverActiveList.size) { i -> minFailOverActiveList[i] })
// ACTIVITY
overviewData.activitySeries = FixedLineGraphSeries(Array(actArrayHist.size) { i -> actArrayHist[i] }).also {
it.isDrawBackground = false
it.color = resourceHelper.gc(R.color.activity)
it.thickness = 3
}
overviewData.activityPredictionSeries = FixedLineGraphSeries(Array(actArrayPrediction.size) { i -> actArrayPrediction[i] }).also {
it.setCustomPaint(Paint().also { paint ->
paint.style = Paint.Style.STROKE
paint.strokeWidth = 3f
paint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f)
paint.color = resourceHelper.gc(R.color.activity)
})
}
// BGI
overviewData.minusBgiSeries = FixedLineGraphSeries(Array(bgiArrayHist.size) { i -> bgiArrayHist[i] }).also {
it.isDrawBackground = false
it.color = resourceHelper.gc(R.color.bgi)
it.thickness = 3
}
overviewData.minusBgiHistSeries = FixedLineGraphSeries(Array(bgiArrayPrediction.size) { i -> bgiArrayPrediction[i] }).also {
it.setCustomPaint(Paint().also { paint ->
paint.style = Paint.Style.STROKE
paint.strokeWidth = 3f
paint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f)
paint.color = resourceHelper.gc(R.color.bgi)
})
}
// DEVIATIONS
overviewData.deviationsSeries = BarGraphSeries(Array(devArray.size) { i -> devArray[i] }).also {
it.setValueDependentColor { data: DeviationDataPoint -> data.color }
}
// RATIO
overviewData.ratioSeries = LineGraphSeries(Array(ratioArray.size) { i -> ratioArray[i] }).also {
it.color = resourceHelper.gc(R.color.ratio)
it.thickness = 3
}
// DEV SLOPE
overviewData.dsMaxSeries = LineGraphSeries(Array(dsMaxArray.size) { i -> dsMaxArray[i] }).also {
it.color = resourceHelper.gc(R.color.devslopepos)
it.thickness = 3
}
overviewData.dsMinSeries = LineGraphSeries(Array(dsMinArray.size) { i -> dsMinArray[i] }).also {
it.color = resourceHelper.gc(R.color.devslopeneg)
it.thickness = 3
}
// profiler.log(LTag.UI, "prepareIobAutosensData() $from", start)
}
private fun addUpperChartMargin(maxBgValue: Double) =
if (profileFunction.getUnits() == GlucoseUnit.MGDL) Round.roundTo(maxBgValue, 40.0) + 80 else Round.roundTo(maxBgValue, 2.0) + 4
private fun getNearestBg(date: Long): Double {
overviewData.bgReadingsArray.let { bgReadingsArray ->
for (reading in bgReadingsArray) {
if (reading.timestamp > date) continue
return Profile.fromMgdlToUnits(reading.value, profileFunction.getUnits())
}
return if (bgReadingsArray.isNotEmpty()) Profile.fromMgdlToUnits(bgReadingsArray[0].value, profileFunction.getUnits())
else Profile.fromMgdlToUnits(100.0, profileFunction.getUnits())
}
}
private fun <E : DataPointWithLabelInterface> List<E>.filterTimeframe(fromTime: Long, endTime: Long): List<E> =
filter { it.x + it.duration >= fromTime && it.x <= endTime }
}

View file

@ -3,4 +3,4 @@ package info.nightscout.androidaps.plugins.general.overview.events
import info.nightscout.androidaps.events.Event import info.nightscout.androidaps.events.Event
import info.nightscout.androidaps.plugins.general.overview.OverviewData import info.nightscout.androidaps.plugins.general.overview.OverviewData
class EventUpdateOverview(val what: OverviewData.Property) : Event() class EventUpdateOverview(val from: String, val what: OverviewData.Property) : Event()

View file

@ -4,52 +4,39 @@ import android.graphics.Color
import android.graphics.DashPathEffect import android.graphics.DashPathEffect
import android.graphics.Paint import android.graphics.Paint
import com.jjoe64.graphview.GraphView import com.jjoe64.graphview.GraphView
import com.jjoe64.graphview.series.BarGraphSeries
import com.jjoe64.graphview.series.DataPoint import com.jjoe64.graphview.series.DataPoint
import com.jjoe64.graphview.series.LineGraphSeries import com.jjoe64.graphview.series.LineGraphSeries
import com.jjoe64.graphview.series.Series import com.jjoe64.graphview.series.Series
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.IobTotal import info.nightscout.androidaps.interfaces.GlucoseUnit
import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.database.entities.Bolus
import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.extensions.target
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.plugins.general.overview.OverviewData
import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults import info.nightscout.androidaps.plugins.general.overview.graphExtensions.AreaGraphSeries
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.* import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DoubleDataPoint
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult import info.nightscout.androidaps.plugins.general.overview.graphExtensions.TimeAsXAxisLabelFormatter
import info.nightscout.androidaps.utils.* import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.Round
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.max import kotlin.math.max
import kotlin.math.min
class GraphData( class GraphData(
injector: HasAndroidInjector, injector: HasAndroidInjector,
private val graph: GraphView, private val graph: GraphView
private val iobCobCalculator: IobCobCalculator
) { ) {
// IobCobCalculatorPlugin Cannot be injected: HistoryBrowser
@Inject lateinit var aapsLogger: AAPSLogger @Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var profileFunction: ProfileFunction @Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var resourceHelper: ResourceHelper @Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var databaseHelper: DatabaseHelperInterface
@Inject lateinit var repository: AppRepository
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var defaultValueHelper: DefaultValueHelper @Inject lateinit var defaultValueHelper: DefaultValueHelper
@Inject lateinit var translator: Translator @Inject lateinit var overviewData: OverviewData
var maxY = Double.MIN_VALUE var maxY = Double.MIN_VALUE
private var minY = Double.MAX_VALUE private var minY = Double.MAX_VALUE
private var bgReadingsArray: List<GlucoseValue>? = null
private val units: GlucoseUnit private val units: GlucoseUnit
private val series: MutableList<Series<*>> = ArrayList() private val series: MutableList<Series<*>> = ArrayList()
@ -58,585 +45,130 @@ class GraphData(
units = profileFunction.getUnits() units = profileFunction.getUnits()
} }
fun addBucketedData(fromTime: Long, toTime: Long) { fun addBucketedData() {
val bucketedData = iobCobCalculator.ads.getBucketedDataTableCopy() ?: return addSeries(overviewData.bucketedGraphSeries)
if (bucketedData.isEmpty()) {
aapsLogger.debug("No bucketed data.")
return
}
val bucketedListArray: MutableList<DataPointWithLabelInterface> = ArrayList()
for (inMemoryGlucoseValue in bucketedData) {
if (inMemoryGlucoseValue.timestamp < fromTime || inMemoryGlucoseValue.timestamp > toTime) continue
bucketedListArray.add(InMemoryGlucoseValueDataPoint(inMemoryGlucoseValue, profileFunction, resourceHelper))
}
addSeries(PointsWithLabelGraphSeries(Array(bucketedListArray.size) { i -> bucketedListArray[i] }))
} }
fun addBgReadings(fromTime: Long, toTime: Long, highLine: Double, predictions: MutableList<GlucoseValueDataPoint>?) { fun addBgReadings(addPredictions: Boolean) {
var maxBgValue = Double.MIN_VALUE maxY = if (overviewData.bgReadingsArray.isEmpty()) {
bgReadingsArray = repository.compatGetBgReadingsDataFromTime(fromTime, toTime, false).blockingGet() if (units == GlucoseUnit.MGDL) 180.0 else 10.0
if (bgReadingsArray?.isEmpty() != false) { } else overviewData.maxBgValue
aapsLogger.debug("No BG data.")
maxY = if (units == GlucoseUnit.MGDL) 180.0 else 10.0
minY = 0.0 minY = 0.0
return addSeries(overviewData.bgReadingGraphSeries)
if (addPredictions) addSeries(overviewData.predictionsGraphSeries)
} }
val bgListArray: MutableList<DataPointWithLabelInterface> = ArrayList()
for (bg in bgReadingsArray!!) {
if (bg.timestamp < fromTime || bg.timestamp > toTime) continue
if (bg.value > maxBgValue) maxBgValue = bg.value
bgListArray.add(GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, resourceHelper))
}
if (predictions != null) {
predictions.sortWith(Comparator { o1: GlucoseValueDataPoint, o2: GlucoseValueDataPoint -> o1.x.compareTo(o2.x) })
for (prediction in predictions) if (prediction.data.value >= 40) bgListArray.add(prediction)
}
maxBgValue = Profile.fromMgdlToUnits(maxBgValue, units)
maxBgValue = addUpperChartMargin(maxBgValue)
if (highLine > maxBgValue) maxBgValue = highLine
maxY = maxBgValue
minY = 0.0
addSeries(PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] }))
}
private fun addUpperChartMargin(maxBgValue: Double) =
if (units == GlucoseUnit.MGDL) Round.roundTo(maxBgValue, 40.0) + 80 else Round.roundTo(maxBgValue, 2.0) + 4
fun addInRangeArea(fromTime: Long, toTime: Long, lowLine: Double, highLine: Double) { fun addInRangeArea(fromTime: Long, toTime: Long, lowLine: Double, highLine: Double) {
val inRangeAreaSeries: AreaGraphSeries<DoubleDataPoint>
val inRangeAreaDataPoints = arrayOf( val inRangeAreaDataPoints = arrayOf(
DoubleDataPoint(fromTime.toDouble(), lowLine, highLine), DoubleDataPoint(fromTime.toDouble(), lowLine, highLine),
DoubleDataPoint(toTime.toDouble(), lowLine, highLine) DoubleDataPoint(toTime.toDouble(), lowLine, highLine)
) )
inRangeAreaSeries = AreaGraphSeries(inRangeAreaDataPoints) addSeries(AreaGraphSeries(inRangeAreaDataPoints).also {
inRangeAreaSeries.color = 0 it.color = 0
inRangeAreaSeries.isDrawBackground = true
inRangeAreaSeries.backgroundColor = resourceHelper.gc(R.color.inrangebackground)
addSeries(inRangeAreaSeries)
}
// scale in % of vertical size (like 0.3)
fun addBasals(fromTime: Long, toTime: Long, scale: Double) {
var maxBasalValueFound = 0.0
val basalScale = Scale()
val baseBasalArray: MutableList<ScaledDataPoint> = ArrayList()
val tempBasalArray: MutableList<ScaledDataPoint> = ArrayList()
val basalLineArray: MutableList<ScaledDataPoint> = ArrayList()
val absoluteBasalLineArray: MutableList<ScaledDataPoint> = ArrayList()
var lastLineBasal = 0.0
var lastAbsoluteLineBasal = -1.0
var lastBaseBasal = 0.0
var lastTempBasal = 0.0
var time = fromTime
while (time < toTime) {
val profile = profileFunction.getProfile(time)
if (profile == null) {
time += 60 * 1000L
continue
}
val basalData = iobCobCalculator.getBasalData(profile, time)
val baseBasalValue = basalData.basal
var absoluteLineValue = baseBasalValue
var tempBasalValue = 0.0
var basal = 0.0
if (basalData.isTempBasalRunning) {
tempBasalValue = basalData.tempBasalAbsolute
absoluteLineValue = tempBasalValue
if (tempBasalValue != lastTempBasal) {
tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, basalScale))
tempBasalArray.add(ScaledDataPoint(time, tempBasalValue.also { basal = it }, basalScale))
}
if (lastBaseBasal != 0.0) {
baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, basalScale))
baseBasalArray.add(ScaledDataPoint(time, 0.0, basalScale))
lastBaseBasal = 0.0
}
} else {
if (baseBasalValue != lastBaseBasal) {
baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, basalScale))
baseBasalArray.add(ScaledDataPoint(time, baseBasalValue.also { basal = it }, basalScale))
lastBaseBasal = baseBasalValue
}
if (lastTempBasal != 0.0) {
tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, basalScale))
tempBasalArray.add(ScaledDataPoint(time, 0.0, basalScale))
}
}
if (baseBasalValue != lastLineBasal) {
basalLineArray.add(ScaledDataPoint(time, lastLineBasal, basalScale))
basalLineArray.add(ScaledDataPoint(time, baseBasalValue, basalScale))
}
if (absoluteLineValue != lastAbsoluteLineBasal) {
absoluteBasalLineArray.add(ScaledDataPoint(time, lastAbsoluteLineBasal, basalScale))
absoluteBasalLineArray.add(ScaledDataPoint(time, basal, basalScale))
}
lastAbsoluteLineBasal = absoluteLineValue
lastLineBasal = baseBasalValue
lastTempBasal = tempBasalValue
maxBasalValueFound = max(maxBasalValueFound, max(tempBasalValue, baseBasalValue))
time += 60 * 1000L
}
// final points
basalLineArray.add(ScaledDataPoint(toTime, lastLineBasal, basalScale))
baseBasalArray.add(ScaledDataPoint(toTime, lastBaseBasal, basalScale))
tempBasalArray.add(ScaledDataPoint(toTime, lastTempBasal, basalScale))
absoluteBasalLineArray.add(ScaledDataPoint(toTime, lastAbsoluteLineBasal, basalScale))
// create series
addSeries(LineGraphSeries(Array(baseBasalArray.size) { i -> baseBasalArray[i] }).also {
it.isDrawBackground = true it.isDrawBackground = true
it.backgroundColor = resourceHelper.gc(R.color.basebasal) it.backgroundColor = resourceHelper.gc(R.color.inrangebackground)
it.thickness = 0
})
addSeries(LineGraphSeries(Array(tempBasalArray.size) { i -> tempBasalArray[i] }).also {
it.isDrawBackground = true
it.backgroundColor = resourceHelper.gc(R.color.tempbasal)
it.thickness = 0
})
addSeries(LineGraphSeries(Array(basalLineArray.size) { i -> basalLineArray[i] }).also {
it.setCustomPaint(Paint().also { paint ->
paint.style = Paint.Style.STROKE
paint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2
paint.pathEffect = DashPathEffect(floatArrayOf(2f, 4f), 0f)
paint.color = resourceHelper.gc(R.color.basal)
})
})
addSeries(LineGraphSeries(Array(absoluteBasalLineArray.size) { i -> absoluteBasalLineArray[i] }).also {
it.setCustomPaint(Paint().also { absolutePaint ->
absolutePaint.style = Paint.Style.STROKE
absolutePaint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2
absolutePaint.color = resourceHelper.gc(R.color.basal)
})
})
basalScale.setMultiplier(maxY * scale / maxBasalValueFound)
}
fun addTargetLine(fromTime: Long, toTimeParam: Long, profile: Profile, lastRun: LoopInterface.LastRun?) {
var toTime = toTimeParam
val targetsSeriesArray: MutableList<DataPoint> = ArrayList()
var lastTarget = -1.0
lastRun?.constraintsProcessed?.let { toTime = max(it.latestPredictionsTime, toTime) }
var time = fromTime
while (time < toTime) {
val tt = repository.getTemporaryTargetActiveAt(time).blockingGet()
val value: Double = if (tt is ValueWrapper.Existing) {
Profile.fromMgdlToUnits(tt.value.target(), units)
} else {
Profile.fromMgdlToUnits((profile.getTargetLowMgdl(time) + profile.getTargetHighMgdl(time)) / 2, units)
}
if (lastTarget != value) {
if (lastTarget != -1.0) targetsSeriesArray.add(DataPoint(time.toDouble(), lastTarget))
targetsSeriesArray.add(DataPoint(time.toDouble(), value))
}
lastTarget = value
time += 5 * 60 * 1000L
}
// final point
targetsSeriesArray.add(DataPoint(toTime.toDouble(), lastTarget))
// create series
addSeries(LineGraphSeries(Array(targetsSeriesArray.size) { i -> targetsSeriesArray[i] }).also {
it.isDrawBackground = false
it.color = resourceHelper.gc(R.color.tempTargetBackground)
it.thickness = 2
}) })
} }
fun addTreatments(fromTime: Long, endTime: Long) { fun addBasals() {
val filteredTreatments: MutableList<DataPointWithLabelInterface> = ArrayList() val scale = defaultValueHelper.determineLowLine() / maxY / 1.2
repository.getBolusesIncludingInvalidFromTimeToTime(fromTime, endTime, true).blockingGet() addSeries(overviewData.baseBasalGraphSeries)
.map { BolusDataPoint(it, resourceHelper, activePlugin, defaultValueHelper) } addSeries(overviewData.tempBasalGraphSeries)
.filter { it.data.type != Bolus.Type.SMB || it.data.isValid } addSeries(overviewData.basalLineGraphSeries)
.forEach { addSeries(overviewData.absoluteBasalGraphSeries)
it.y = getNearestBg(it.x.toLong()) overviewData.basalScale.setMultiplier(maxY * scale / overviewData.maxBasalValueFound)
filteredTreatments.add(it)
}
repository.getCarbsIncludingInvalidFromTimeToTimeExpanded(fromTime, endTime, true).blockingGet()
.map { CarbsDataPoint(it, resourceHelper) }
.forEach {
it.y = getNearestBg(it.x.toLong())
filteredTreatments.add(it)
} }
// ProfileSwitch fun addTargetLine() {
repository.getEffectiveProfileSwitchDataFromTimeToTime(fromTime, endTime, true).blockingGet() addSeries(overviewData.temporaryTargetSeries)
.map { EffectiveProfileSwitchDataPoint(it) }
.forEach(filteredTreatments::add)
// Extended bolus
if (!activePlugin.activePump.isFakingTempsByExtendedBoluses) {
repository.getExtendedBolusDataFromTimeToTime(fromTime, endTime, true).blockingGet()
.map { ExtendedBolusDataPoint(it) }
.filter { it.duration != 0L }
.forEach {
it.y = getNearestBg(it.x.toLong())
filteredTreatments.add(it)
}
} }
// Careportal fun addTreatments() {
repository.compatGetTherapyEventDataFromToTime(fromTime - T.hours(6).msecs(), endTime).blockingGet() maxY = maxOf(maxY, overviewData.maxTreatmentsValue)
.map { TherapyEventDataPoint(it, resourceHelper, profileFunction, translator) } addSeries(overviewData.treatmentsSeries)
.filterTimeframe(fromTime, endTime)
.forEach {
if (it.y == 0.0) it.y = getNearestBg(it.x.toLong())
filteredTreatments.add(it)
} }
// increase maxY if a treatment forces it's own height that's higher than a BG value fun addActivity(scale: Double) {
filteredTreatments.map { it.y } addSeries(overviewData.activitySeries)
.maxOrNull() addSeries(overviewData.activityPredictionSeries)
?.let(::addUpperChartMargin) overviewData.actScale.setMultiplier(maxY * scale / overviewData.maxIAValue)
?.let { maxY = maxOf(maxY, it) }
addSeries(PointsWithLabelGraphSeries(filteredTreatments.toTypedArray()))
}
private fun getNearestBg(date: Long): Double {
bgReadingsArray?.let { bgReadingsArray ->
for (reading in bgReadingsArray) {
if (reading.timestamp > date) continue
return Profile.fromMgdlToUnits(reading.value, units)
}
return if (bgReadingsArray.isNotEmpty()) Profile.fromMgdlToUnits(bgReadingsArray[0].value, units) else Profile.fromMgdlToUnits(100.0, units)
} ?: return Profile.fromMgdlToUnits(100.0, units)
}
fun addActivity(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) {
val actArrayHist: MutableList<ScaledDataPoint> = ArrayList()
val actArrayPrediction: MutableList<ScaledDataPoint> = ArrayList()
val now = System.currentTimeMillis().toDouble()
val actScale = Scale()
var total: IobTotal
var maxIAValue = 0.0
var time = fromTime
while (time <= toTime) {
val profile = profileFunction.getProfile(time)
if (profile == null) {
time += 5 * 60 * 1000L
continue
}
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))
time += 5 * 60 * 1000L
}
addSeries(FixedLineGraphSeries(Array(actArrayHist.size) { i -> actArrayHist[i] }).also {
it.isDrawBackground = false
it.color = resourceHelper.gc(R.color.activity)
it.thickness = 3
})
addSeries(FixedLineGraphSeries(Array(actArrayPrediction.size) { i -> actArrayPrediction[i] }).also {
it.setCustomPaint(Paint().also { paint ->
paint.style = Paint.Style.STROKE
paint.strokeWidth = 3f
paint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f)
paint.color = resourceHelper.gc(R.color.activity)
})
})
if (useForScale) {
maxY = maxIAValue
minY = -maxIAValue
}
actScale.setMultiplier(maxY * scale / maxIAValue)
} }
//Function below show -BGI to be able to compare curves with deviations //Function below show -BGI to be able to compare curves with deviations
fun addMinusBGI(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double, devBgiScale: Boolean) { fun addMinusBGI(useForScale: Boolean, scale: Double) {
val bgiArrayHist: MutableList<ScaledDataPoint> = ArrayList()
val bgiArrayPrediction: MutableList<ScaledDataPoint> = ArrayList()
val now = System.currentTimeMillis().toDouble()
val bgiScale = Scale()
var total: IobTotal
var maxBGIValue = 0.0
var time = fromTime
while (time <= toTime) {
val profile = profileFunction.getProfile(time)
if (profile == null) {
time += 5 * 60 * 1000L
continue
}
val deviation = if (devBgiScale) iobCobCalculator.ads.getAutosensDataAtTime(time)?.deviation
?: 0.0 else 0.0
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))
time += 5 * 60 * 1000L
}
addSeries(FixedLineGraphSeries(Array(bgiArrayHist.size) { i -> bgiArrayHist[i] }).also {
it.isDrawBackground = false
it.color = resourceHelper.gc(R.color.bgi)
it.thickness = 3
})
addSeries(FixedLineGraphSeries(Array(bgiArrayPrediction.size) { i -> bgiArrayPrediction[i] }).also {
it.setCustomPaint(Paint().also { paint ->
paint.style = Paint.Style.STROKE
paint.strokeWidth = 3f
paint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f)
paint.color = resourceHelper.gc(R.color.bgi)
})
})
if (useForScale) { if (useForScale) {
maxY = maxBGIValue maxY = overviewData.maxBGIValue
minY = -maxBGIValue minY = -overviewData.maxBGIValue
} }
bgiScale.setMultiplier(maxY * scale / maxBGIValue) overviewData.bgiScale.setMultiplier(maxY * scale / overviewData.maxBGIValue)
addSeries(overviewData.minusBgiSeries)
addSeries(overviewData.minusBgiHistSeries)
} }
// scale in % of vertical size (like 0.3) // scale in % of vertical size (like 0.3)
fun addIob(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double, showPrediction: Boolean, absScale: Boolean) { fun addIob(useForScale: Boolean, scale: Double) {
val iobSeries: FixedLineGraphSeries<ScaledDataPoint?>
val iobArray: MutableList<ScaledDataPoint> = ArrayList()
var maxIobValueFound = Double.MIN_VALUE
var lastIob = 0.0
val iobScale = Scale()
var time = fromTime
while (time <= toTime) {
val profile = profileFunction.getProfile(time)
if (profile == null) {
time += 5 * 60 * 1000L
continue
}
var absIob = 0.0
val iob: Double = 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))
iobArray.add(ScaledDataPoint(time, iob, iobScale))
maxIobValueFound = if (absScale) max(maxIobValueFound, abs(absIob)) else max(maxIobValueFound, abs(iob))
lastIob = iob
}
time += 5 * 60 * 1000L
}
iobSeries = FixedLineGraphSeries(Array(iobArray.size) { i -> iobArray[i] }).also {
it.isDrawBackground = true
it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.iob) //50%
it.color = resourceHelper.gc(R.color.iob)
it.thickness = 3
}
if (showPrediction) {
val autosensData = iobCobCalculator.getLastAutosensDataWithWaitForCalculationFinish("GraphData")
val lastAutosensResult = autosensData?.autosensResult ?: AutosensResult()
val isTempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() is ValueWrapper.Existing
val iobPrediction: MutableList<DataPointWithLabelInterface> = ArrayList()
val iobPredictionArray = iobCobCalculator.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget)
for (i in iobPredictionArray) {
iobPrediction.add(i.setColor(resourceHelper.gc(R.color.iobPredAS)))
maxIobValueFound = max(maxIobValueFound, abs(i.iob))
}
addSeries(PointsWithLabelGraphSeries(Array(iobPrediction.size) { i -> iobPrediction[i] }))
val iobPrediction2: MutableList<DataPointWithLabelInterface> = ArrayList()
val iobPredictionArray2 = iobCobCalculator.calculateIobArrayForSMB(AutosensResult(), SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget)
for (i in iobPredictionArray2) {
iobPrediction2.add(i.setColor(resourceHelper.gc(R.color.iobPred)))
maxIobValueFound = max(maxIobValueFound, abs(i.iob))
}
addSeries(PointsWithLabelGraphSeries(Array(iobPrediction2.size) { i -> iobPrediction2[i] }))
aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + iobCobCalculator.iobArrayToString(iobPredictionArray))
aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(1.0) + ": " + iobCobCalculator.iobArrayToString(iobPredictionArray2))
}
if (useForScale) { if (useForScale) {
maxY = maxIobValueFound maxY = overviewData.maxIobValueFound
minY = -maxIobValueFound minY = -overviewData.maxIobValueFound
} }
iobScale.setMultiplier(maxY * scale / maxIobValueFound) overviewData.iobScale.setMultiplier(maxY * scale / overviewData.maxIobValueFound)
addSeries(iobSeries) addSeries(overviewData.iobSeries)
addSeries(overviewData.iobPredictions1Series)
addSeries(overviewData.iobPredictions2Series)
} }
// scale in % of vertical size (like 0.3) // scale in % of vertical size (like 0.3)
fun addAbsIob(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { fun addAbsIob(useForScale: Boolean, scale: Double) {
val iobSeries: FixedLineGraphSeries<ScaledDataPoint?>
val iobArray: MutableList<ScaledDataPoint> = ArrayList()
var maxIobValueFound = Double.MIN_VALUE
var lastIob = 0.0
val iobScale = Scale()
var time = fromTime
while (time <= toTime) {
val profile = profileFunction.getProfile(time)
if (profile == null) {
time += 5 * 60 * 1000L
continue
}
val iob: Double = 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))
maxIobValueFound = max(maxIobValueFound, abs(iob))
lastIob = iob
}
time += 5 * 60 * 1000L
}
iobSeries = FixedLineGraphSeries(Array(iobArray.size) { i -> iobArray[i] }).also {
it.isDrawBackground = true
it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.iob) //50%
it.color = resourceHelper.gc(R.color.iob)
it.thickness = 3
}
if (useForScale) { if (useForScale) {
maxY = maxIobValueFound maxY = overviewData.maxIobValueFound
minY = -maxIobValueFound minY = -overviewData.maxIobValueFound
} }
iobScale.setMultiplier(maxY * scale / maxIobValueFound) overviewData.iobScale.setMultiplier(maxY * scale / overviewData.maxIobValueFound)
addSeries(iobSeries) addSeries(overviewData.absIobSeries)
} }
// scale in % of vertical size (like 0.3) // scale in % of vertical size (like 0.3)
fun addCob(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { fun addCob(useForScale: Boolean, scale: Double) {
val minFailOverActiveList: MutableList<DataPointWithLabelInterface> = ArrayList()
val cobArray: MutableList<ScaledDataPoint> = ArrayList()
var maxCobValueFound = 0.0
var lastCob = 0
val cobScale = Scale()
var time = fromTime
while (time <= toTime) {
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))
cobArray.add(ScaledDataPoint(time, cob.toDouble(), cobScale))
maxCobValueFound = max(maxCobValueFound, cob.toDouble())
lastCob = cob
}
if (autosensData.failoverToMinAbsorbtionRate) {
autosensData.setScale(cobScale)
autosensData.setChartTime(time)
minFailOverActiveList.add(autosensData)
}
}
time += 5 * 60 * 1000L
}
// COB
addSeries(FixedLineGraphSeries(Array(cobArray.size) { i -> cobArray[i] }).also {
it.isDrawBackground = true
it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.cob) //50%
it.color = resourceHelper.gc(R.color.cob)
it.thickness = 3
})
if (useForScale) { if (useForScale) {
maxY = maxCobValueFound maxY = overviewData.maxCobValueFound
minY = 0.0 minY = 0.0
} }
cobScale.setMultiplier(maxY * scale / maxCobValueFound) overviewData.cobScale.setMultiplier(maxY * scale / overviewData.maxCobValueFound)
addSeries(PointsWithLabelGraphSeries(Array(minFailOverActiveList.size) { i -> minFailOverActiveList[i] })) addSeries(overviewData.cobSeries)
addSeries(overviewData.cobMinFailOverSeries)
} }
// scale in % of vertical size (like 0.3) // scale in % of vertical size (like 0.3)
fun addDeviations(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double, devBgiScale: Boolean) { fun addDeviations(useForScale: Boolean, scale: Double) {
class DeviationDataPoint(x: Double, y: Double, var color: Int, scale: Scale) : ScaledDataPoint(x, y, scale)
val devArray: MutableList<DeviationDataPoint> = ArrayList()
var maxDevValueFound = 0.0
val devScale = Scale()
var time = fromTime
var total: IobTotal
while (time <= toTime) {
// 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)
if (profile == null) {
time += 5 * 60 * 1000L
continue
}
total = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile)
total.activity * profile.getIsfMgdl(time) * 5.0
} else 0.0
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)
if (autosensData.pastSensitivity == "+") color = resourceHelper.gc(R.color.deviationgreen)
if (autosensData.pastSensitivity == "-") color = resourceHelper.gc(R.color.deviationred)
} else if (autosensData.type == "uam") {
color = resourceHelper.gc(R.color.uam)
} else if (autosensData.type == "csf") {
color = resourceHelper.gc(R.color.deviationgrey)
}
devArray.add(DeviationDataPoint(time.toDouble(), autosensData.deviation, color, devScale))
maxDevValueFound = max(maxDevValueFound, max(abs(autosensData.deviation), abs(bgi)))
}
time += 5 * 60 * 1000L
}
// DEVIATIONS
addSeries(BarGraphSeries(Array(devArray.size) { i -> devArray[i] }).also {
it.setValueDependentColor { data: DeviationDataPoint -> data.color }
})
if (useForScale) { if (useForScale) {
maxY = maxDevValueFound maxY = overviewData.maxDevValueFound
minY = -maxY minY = -maxY
} }
devScale.setMultiplier(maxY * scale / maxDevValueFound) overviewData.devScale.setMultiplier(maxY * scale / overviewData.maxDevValueFound)
addSeries(overviewData.deviationsSeries)
} }
// scale in % of vertical size (like 0.3) // scale in % of vertical size (like 0.3)
fun addRatio(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { fun addRatio(useForScale: Boolean, scale: Double) {
val ratioArray: MutableList<ScaledDataPoint> = ArrayList()
var maxRatioValueFound = 5.0 //even if sens data equals 0 for all the period, minimum scale is between 95% and 105%
var minRatioValueFound = -maxRatioValueFound
val ratioScale = if (useForScale) Scale(100.0) else Scale()
var time = fromTime
while (time <= toTime) {
iobCobCalculator.ads.getAutosensDataAtTime(time)?.let { autosensData ->
ratioArray.add(ScaledDataPoint(time, 100.0 * (autosensData.autosensResult.ratio - 1), ratioScale))
maxRatioValueFound = max(maxRatioValueFound, 100.0 * (autosensData.autosensResult.ratio - 1))
minRatioValueFound = min(minRatioValueFound, 100.0 * (autosensData.autosensResult.ratio - 1))
}
time += 5 * 60 * 1000L
}
// RATIOS
addSeries(LineGraphSeries(Array(ratioArray.size) { i -> ratioArray[i] }).also {
it.color = resourceHelper.gc(R.color.ratio)
it.thickness = 3
})
if (useForScale) { if (useForScale) {
maxY = 100.0 + max(maxRatioValueFound, abs(minRatioValueFound)) maxY = 100.0 + max(overviewData.maxRatioValueFound, abs(overviewData.minRatioValueFound))
minY = 100.0 - max(maxRatioValueFound, abs(minRatioValueFound)) minY = 100.0 - max(overviewData.maxRatioValueFound, abs(overviewData.minRatioValueFound))
ratioScale.setMultiplier(1.0) overviewData.ratioScale.setMultiplier(1.0)
} else } else
ratioScale.setMultiplier(maxY * scale / max(maxRatioValueFound, abs(minRatioValueFound))) overviewData.ratioScale.setMultiplier(maxY * scale / max(overviewData.maxRatioValueFound, abs(overviewData.minRatioValueFound)))
addSeries(overviewData.ratioSeries)
} }
// scale in % of vertical size (like 0.3) // scale in % of vertical size (like 0.3)
fun addDeviationSlope(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { fun addDeviationSlope(useForScale: Boolean, scale: Double) {
val dsMaxArray: MutableList<ScaledDataPoint> = ArrayList()
val dsMinArray: MutableList<ScaledDataPoint> = ArrayList()
var maxFromMaxValueFound = 0.0
var maxFromMinValueFound = 0.0
val dsMaxScale = Scale()
val dsMinScale = Scale()
var time = fromTime
while (time <= toTime) {
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))
maxFromMinValueFound = max(maxFromMinValueFound, abs(autosensData.slopeFromMinDeviation))
}
time += 5 * 60 * 1000L
}
// Slopes
addSeries(LineGraphSeries(Array(dsMaxArray.size) { i -> dsMaxArray[i] }).also {
it.color = resourceHelper.gc(R.color.devslopepos)
it.thickness = 3
})
addSeries(LineGraphSeries(Array(dsMinArray.size) { i -> dsMinArray[i] }).also {
it.color = resourceHelper.gc(R.color.devslopeneg)
it.thickness = 3
})
if (useForScale) { if (useForScale) {
maxY = max(maxFromMaxValueFound, maxFromMinValueFound) maxY = max(overviewData.maxFromMaxValueFound, overviewData.maxFromMinValueFound)
minY = -maxY minY = -maxY
} }
dsMaxScale.setMultiplier(maxY * scale / maxFromMaxValueFound) overviewData.dsMaxScale.setMultiplier(maxY * scale / overviewData.maxFromMaxValueFound)
dsMinScale.setMultiplier(maxY * scale / maxFromMinValueFound) overviewData.dsMinScale.setMultiplier(maxY * scale / overviewData.maxFromMinValueFound)
addSeries(overviewData.dsMaxSeries)
addSeries(overviewData.dsMinSeries)
} }
// scale in % of vertical size (like 0.3) // scale in % of vertical size (like 0.3)
@ -669,7 +201,7 @@ class GraphData(
graph.gridLabelRenderer.numHorizontalLabels = 7 // only 7 because of the space graph.gridLabelRenderer.numHorizontalLabels = 7 // only 7 because of the space
} }
private fun addSeries(s: Series<*>) = series.add(s) internal fun addSeries(s: Series<*>) = series.add(s)
fun performUpdate() { fun performUpdate() {
// clear old data // clear old data
@ -693,5 +225,3 @@ class GraphData(
} }
} }
private fun <E : DataPointWithLabelInterface> List<E>.filterTimeframe(fromTime: Long, endTime: Long): List<E> =
filter { it.x + it.duration >= fromTime && it.x <= endTime }

View file

@ -7,7 +7,6 @@ import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.IobTotal import info.nightscout.androidaps.data.IobTotal
import info.nightscout.androidaps.data.MealData import info.nightscout.androidaps.data.MealData
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.database.entities.Bolus import info.nightscout.androidaps.database.entities.Bolus
@ -71,7 +70,6 @@ open class IobCobCalculatorPlugin @Inject constructor(
private val disposable = CompositeDisposable() private val disposable = CompositeDisposable()
private var iobTable = LongSparseArray<IobTotal>() // oldest at index 0 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 basalDataTable = LongSparseArray<BasalData>() // oldest at index 0 private var basalDataTable = LongSparseArray<BasalData>() // oldest at index 0
override var ads: AutosensDataStore = AutosensDataStore() override var ads: AutosensDataStore = AutosensDataStore()
@ -169,10 +167,9 @@ open class IobCobCalculatorPlugin @Inject constructor(
return getBGDataFrom return getBGDataFrom
} }
override fun calculateFromTreatmentsAndTemps(fromTime: Long, profile: Profile): IobTotal { override fun calculateFromTreatmentsAndTemps(toTime: Long, profile: Profile): IobTotal {
synchronized(dataLock) {
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
val time = ads.roundUpTime(fromTime) val time = ads.roundUpTime(toTime)
val cacheHit = iobTable[time] val cacheHit = iobTable[time]
if (time < now && cacheHit != null) { if (time < now && cacheHit != null) {
//og.debug(">>> calculateFromTreatmentsAndTemps Cache hit " + new Date(time).toLocaleString()); //og.debug(">>> calculateFromTreatmentsAndTemps Cache hit " + new Date(time).toLocaleString());
@ -196,30 +193,12 @@ open class IobCobCalculatorPlugin @Inject constructor(
basalIob.iobWithZeroTemp = IobTotal.combine(bolusIob, basalIobWithZeroTemp).round() basalIob.iobWithZeroTemp = IobTotal.combine(bolusIob, basalIobWithZeroTemp).round()
val iobTotal = IobTotal.combine(bolusIob, basalIob).round() val iobTotal = IobTotal.combine(bolusIob, basalIob).round()
if (time < System.currentTimeMillis()) { if (time < System.currentTimeMillis()) {
synchronized(dataLock) {
iobTable.put(time, iobTotal) iobTable.put(time, iobTotal)
} }
return iobTotal
}
}
override fun calculateAbsInsulinFromTreatmentsAndTemps(fromTime: Long): IobTotal {
synchronized(dataLock) {
val now = System.currentTimeMillis()
val time = ads.roundUpTime(fromTime)
val cacheHit = absIobTable[time]
if (time < now && cacheHit != null) {
//log.debug(">>> calculateFromTreatmentsAndTemps Cache hit " + new Date(time).toLocaleString());
return cacheHit
} // else log.debug(">>> calculateFromTreatmentsAndTemps Cache miss " + new Date(time).toLocaleString());
val bolusIob = calculateIobFromBolusToTime(time).round()
val basalIob = calculateAbsoluteIobTempBasals(time).round()
val iobTotal = IobTotal.combine(bolusIob, basalIob).round()
if (time < System.currentTimeMillis()) {
absIobTable.put(time, iobTotal)
} }
return iobTotal return iobTotal
} }
}
private fun calculateFromTreatmentsAndTemps(time: Long, lastAutosensResult: AutosensResult, exercise_mode: Boolean, half_basal_exercise_target: Int, isTempTarget: Boolean): IobTotal { private fun calculateFromTreatmentsAndTemps(time: Long, lastAutosensResult: AutosensResult, exercise_mode: Boolean, half_basal_exercise_target: Int, isTempTarget: Boolean): IobTotal {
val now = dateUtil.now() val now = dateUtil.now()
@ -246,7 +225,6 @@ open class IobCobCalculatorPlugin @Inject constructor(
} }
override fun getBasalData(profile: Profile, fromTime: Long): BasalData { override fun getBasalData(profile: Profile, fromTime: Long): BasalData {
synchronized(dataLock) {
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
val time = ads.roundUpTime(fromTime) val time = ads.roundUpTime(fromTime)
var retVal = basalDataTable[time] var retVal = basalDataTable[time]
@ -263,12 +241,13 @@ open class IobCobCalculatorPlugin @Inject constructor(
retVal.tempBasalAbsolute = retVal.basal retVal.tempBasalAbsolute = retVal.basal
} }
if (time < now) { if (time < now) {
synchronized(dataLock) {
basalDataTable.append(time, retVal) basalDataTable.append(time, retVal)
} }
}
} //else log.debug(">>> getBasalData Cache hit " + new Date(time).toLocaleString()); } //else log.debug(">>> getBasalData Cache hit " + new Date(time).toLocaleString());
return retVal return retVal
} }
}
override fun getLastAutosensDataWithWaitForCalculationFinish(reason: String): AutosensData? { override fun getLastAutosensDataWithWaitForCalculationFinish(reason: String): AutosensData? {
if (thread?.isAlive == true) { if (thread?.isAlive == true) {
@ -408,14 +387,6 @@ open class IobCobCalculatorPlugin @Inject constructor(
break break
} }
} }
for (index in absIobTable.size() - 1 downTo 0) {
if (absIobTable.keyAt(index) > time) {
aapsLogger.debug(LTag.AUTOSENS, "Removing from absIobTable: " + dateUtil.dateAndTimeAndSecondsString(absIobTable.keyAt(index)))
absIobTable.removeAt(index)
} else {
break
}
}
for (index in basalDataTable.size() - 1 downTo 0) { for (index in basalDataTable.size() - 1 downTo 0) {
if (basalDataTable.keyAt(index) > time) { if (basalDataTable.keyAt(index) > time) {
aapsLogger.debug(LTag.AUTOSENS, "Removing from basalDataTable: " + dateUtil.dateAndTimeAndSecondsString(basalDataTable.keyAt(index))) aapsLogger.debug(LTag.AUTOSENS, "Removing from basalDataTable: " + dateUtil.dateAndTimeAndSecondsString(basalDataTable.keyAt(index)))
@ -459,8 +430,8 @@ open class IobCobCalculatorPlugin @Inject constructor(
* Time range to the past for IOB calculation * Time range to the past for IOB calculation
* @return milliseconds * @return milliseconds
*/ */
fun range(): Long = ((profileFunction.getProfile()?.dia fun range(): Long = ((/*overviewData.rangeToDisplay + */(profileFunction.getProfile()?.dia
?: Constants.defaultDIA) * 60 * 60 * 1000).toLong() ?: Constants.defaultDIA)) * 60 * 60 * 1000).toLong()
override fun calculateIobFromBolus(): IobTotal = calculateIobFromBolusToTime(dateUtil.now()) override fun calculateIobFromBolus(): IobTotal = calculateIobFromBolusToTime(dateUtil.now())
@ -533,6 +504,7 @@ open class IobCobCalculatorPlugin @Inject constructor(
} }
override fun getTempBasalIncludingConvertedExtended(timestamp: Long): TemporaryBasal? { override fun getTempBasalIncludingConvertedExtended(timestamp: Long): TemporaryBasal? {
val tb = repository.getTemporaryBasalActiveAt(timestamp).blockingGet() val tb = repository.getTemporaryBasalActiveAt(timestamp).blockingGet()
if (tb is ValueWrapper.Existing) return tb.value if (tb is ValueWrapper.Existing) return tb.value
val eb = repository.getExtendedBolusActiveAt(timestamp).blockingGet() val eb = repository.getExtendedBolusActiveAt(timestamp).blockingGet()
@ -542,7 +514,7 @@ open class IobCobCalculatorPlugin @Inject constructor(
return null return null
} }
override fun calculateAbsoluteIobTempBasals(toTime: Long): IobTotal { override fun calculateAbsoluteIobFromBaseBasals(toTime: Long): IobTotal {
val total = IobTotal(toTime) val total = IobTotal(toTime)
var i = toTime - range() var i = toTime - range()
while (i < toTime) { while (i < toTime) {
@ -551,8 +523,7 @@ open class IobCobCalculatorPlugin @Inject constructor(
i += T.mins(5).msecs() i += T.mins(5).msecs()
continue continue
} }
val runningTBR = getTempBasalIncludingConvertedExtended(i) val running = profile.getBasal(i)
val running = runningTBR?.convertedToAbsolute(i, profile) ?: profile.getBasal(i)
val bolus = Bolus( val bolus = Bolus(
timestamp = i, timestamp = i,
amount = running * 5.0 / 60.0, amount = running * 5.0 / 60.0,

View file

@ -82,7 +82,7 @@ class IobCobOref1Thread internal constructor(
//log.debug("Locking calculateSensitivityData"); //log.debug("Locking calculateSensitivityData");
val oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable) val oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable)
if (bgDataReload) { if (bgDataReload) {
iobCobCalculatorPlugin.ads.loadBgData(end, repository, aapsLogger, dateUtil) iobCobCalculatorPlugin.ads.loadBgData(end, repository, aapsLogger, dateUtil, rxBus)
iobCobCalculatorPlugin.clearCache() iobCobCalculatorPlugin.clearCache()
} }
// work on local copy and set back when finished // work on local copy and set back when finished

View file

@ -81,7 +81,7 @@ class IobCobThread @Inject internal constructor(
//log.debug("Locking calculateSensitivityData"); //log.debug("Locking calculateSensitivityData");
val oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable) val oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable)
if (bgDataReload) { if (bgDataReload) {
iobCobCalculatorPlugin.ads.loadBgData(end, repository, aapsLogger, dateUtil) iobCobCalculatorPlugin.ads.loadBgData(end, repository, aapsLogger, dateUtil, rxBus)
iobCobCalculatorPlugin.clearCache() iobCobCalculatorPlugin.clearCache()
} }
// work on local copy and set back when finished // work on local copy and set back when finished

View file

@ -27,6 +27,7 @@ import info.nightscout.androidaps.utils.sharedPreferences.SP
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
import java.lang.Integer.min
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -413,7 +414,7 @@ class LocalProfilePlugin @Inject constructor(
if (sp.getBoolean(R.string.key_ns_receive_profile_store, false) || config.NSCLIENT) { if (sp.getBoolean(R.string.key_ns_receive_profile_store, false) || config.NSCLIENT) {
localProfilePlugin.loadFromStore(ProfileStore(injector, profileJson, dateUtil)) localProfilePlugin.loadFromStore(ProfileStore(injector, profileJson, dateUtil))
aapsLogger.debug(LTag.PROFILE, "Received profileStore: $profileJson") aapsLogger.debug(LTag.PROFILE, "Received profileStore: $profileJson")
return Result.success(workDataOf("Data" to profileJson.toString().substring(1..5000))) return Result.success(workDataOf("Data" to profileJson.toString().substring(0..min(5000, profileJson.length()))))
} }
return Result.success() return Result.success()
} }

View file

@ -10,9 +10,12 @@ class TrendCalculator @Inject constructor(
private val repository: AppRepository private val repository: AppRepository
) { ) {
fun getTrendArrow(glucoseValue: GlucoseValue): GlucoseValue.TrendArrow = fun getTrendArrow(glucoseValue: GlucoseValue?): GlucoseValue.TrendArrow =
if (glucoseValue.trendArrow != GlucoseValue.TrendArrow.NONE) glucoseValue.trendArrow when {
else calculateDirection(glucoseValue) glucoseValue?.trendArrow == null -> GlucoseValue.TrendArrow.NONE
glucoseValue.trendArrow != GlucoseValue.TrendArrow.NONE -> glucoseValue.trendArrow
else -> calculateDirection(glucoseValue)
}
private fun calculateDirection(glucoseValue: GlucoseValue): GlucoseValue.TrendArrow { private fun calculateDirection(glucoseValue: GlucoseValue): GlucoseValue.TrendArrow {

View file

@ -6,7 +6,6 @@ import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.Round import info.nightscout.androidaps.utils.Round
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
import java.util.*
@Suppress("SpellCheckingInspection") @Suppress("SpellCheckingInspection")
class IobTotal(var time: Long) : DataPointWithLabelInterface { class IobTotal(var time: Long) : DataPointWithLabelInterface {
@ -22,7 +21,6 @@ class IobTotal(var time: Long) : DataPointWithLabelInterface {
@JvmField var lastBolusTime: Long = 0 @JvmField var lastBolusTime: Long = 0
var iobWithZeroTemp: IobTotal? = null var iobWithZeroTemp: IobTotal? = null
@JvmField var netInsulin = 0.0 // for calculations from temp basals only @JvmField var netInsulin = 0.0 // for calculations from temp basals only
@JvmField var netRatio = 0.0 // net ratio at start of temp basal
@JvmField var extendedBolusInsulin = 0.0 // total insulin for extended bolus @JvmField var extendedBolusInsulin = 0.0 // total insulin for extended bolus
fun copy(): IobTotal { fun copy(): IobTotal {
val i = IobTotal(time) val i = IobTotal(time)
@ -33,9 +31,8 @@ class IobTotal(var time: Long) : DataPointWithLabelInterface {
i.netbasalinsulin = netbasalinsulin i.netbasalinsulin = netbasalinsulin
i.hightempinsulin = hightempinsulin i.hightempinsulin = hightempinsulin
i.lastBolusTime = lastBolusTime i.lastBolusTime = lastBolusTime
if (iobWithZeroTemp != null) i.iobWithZeroTemp = iobWithZeroTemp!!.copy() i.iobWithZeroTemp = iobWithZeroTemp?.copy()
i.netInsulin = netInsulin i.netInsulin = netInsulin
i.netRatio = netRatio
i.extendedBolusInsulin = extendedBolusInsulin i.extendedBolusInsulin = extendedBolusInsulin
return i return i
} }

View file

@ -165,7 +165,6 @@ fun TemporaryBasal.iobCalc(time: Long, profile: Profile, insulinInterface: Insul
result.hightempinsulin += tempBolusPart.amount result.hightempinsulin += tempBolusPart.amount
} }
} }
result.netRatio = netBasalRate // ratio at the end of interval
} }
} }
result.netInsulin = netBasalAmount result.netInsulin = netBasalAmount
@ -217,7 +216,6 @@ fun TemporaryBasal.iobCalc(time: Long, profile: Profile, lastAutosensResult: Aut
result.hightempinsulin += tempBolusPart.amount result.hightempinsulin += tempBolusPart.amount
} }
} }
result.netRatio = netBasalRate // ratio at the end of interval
} }
} }
result.netInsulin = netBasalAmount result.netInsulin = netBasalAmount

View file

@ -2,7 +2,6 @@ package info.nightscout.androidaps.interfaces
import info.nightscout.androidaps.data.IobTotal import info.nightscout.androidaps.data.IobTotal
import info.nightscout.androidaps.data.MealData import info.nightscout.androidaps.data.MealData
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.database.entities.ExtendedBolus import info.nightscout.androidaps.database.entities.ExtendedBolus
import info.nightscout.androidaps.database.entities.TemporaryBasal import info.nightscout.androidaps.database.entities.TemporaryBasal
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensDataStore import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensDataStore
@ -19,8 +18,7 @@ interface IobCobCalculator {
fun getMealDataWithWaitingForCalculationFinish(): MealData fun getMealDataWithWaitingForCalculationFinish(): MealData
fun getLastAutosensDataWithWaitForCalculationFinish(reason: String): AutosensData? fun getLastAutosensDataWithWaitForCalculationFinish(reason: String): AutosensData?
fun calculateAbsInsulinFromTreatmentsAndTemps(fromTime: Long): IobTotal fun calculateFromTreatmentsAndTemps(toTime: Long, profile: Profile): IobTotal
fun calculateFromTreatmentsAndTemps(fromTime: Long, profile: Profile): IobTotal
fun getBasalData(profile: Profile, fromTime: Long): BasalData fun getBasalData(profile: Profile, fromTime: Long): BasalData
@ -72,14 +70,12 @@ interface IobCobCalculator {
fun getExtendedBolus(timestamp: Long): ExtendedBolus? fun getExtendedBolus(timestamp: Long): ExtendedBolus?
/** /**
* Calculate IOB of all insulin in the body to the time * Calculate IOB of base basal insulin (usualy not accounted towards IOB)
*
* Running basal is added to the IOB !!!
* *
* @param toTime * @param toTime
* @return IobTotal * @return IobTotal
*/ */
fun calculateAbsoluteIobTempBasals(toTime: Long): IobTotal fun calculateAbsoluteIobFromBaseBasals(toTime: Long): IobTotal
/** /**
* Calculate IOB from Temporary basals and Extended boluses (if emulation is enabled) to the the time specified * Calculate IOB from Temporary basals and Extended boluses (if emulation is enabled) to the the time specified

View file

@ -1,3 +1,9 @@
package info.nightscout.androidaps.interfaces package info.nightscout.androidaps.interfaces
interface Overview : ConfigExportImport import info.nightscout.androidaps.plugins.bus.RxBusWrapper
interface Overview : ConfigExportImport {
fun refreshLoop(from: String)
val overviewBus: RxBusWrapper
}

View file

@ -6,7 +6,9 @@ import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.GlucoseValue import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventBucketedDataCreated
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.T
import kotlin.math.abs import kotlin.math.abs
@ -151,7 +153,7 @@ class AutosensDataStore {
return someTime + diff return someTime + diff
} }
fun loadBgData(to: Long, repository: AppRepository, aapsLogger: AAPSLogger, dateUtil: DateUtil) { fun loadBgData(to: Long, repository: AppRepository, aapsLogger: AAPSLogger, dateUtil: DateUtil, rxBus: RxBusWrapper) {
synchronized(dataLock) { synchronized(dataLock) {
val start = to - T.hours((24 + 10 /* max dia */).toLong()).msecs() 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) // there can be some readings with time in close future (caused by wrong time setting on sensor)
@ -162,6 +164,7 @@ class AutosensDataStore {
.filter { it.value >= 39 } .filter { it.value >= 39 }
aapsLogger.debug(LTag.AUTOSENS, "BG data loaded. Size: " + bgReadings.size + " Start date: " + dateUtil.dateAndTimeString(start) + " End date: " + dateUtil.dateAndTimeString(to)) aapsLogger.debug(LTag.AUTOSENS, "BG data loaded. Size: " + bgReadings.size + " Start date: " + dateUtil.dateAndTimeString(start) + " End date: " + dateUtil.dateAndTimeString(to))
createBucketedData(aapsLogger, dateUtil) createBucketedData(aapsLogger, dateUtil)
rxBus.send(EventBucketedDataCreated())
} }
} }

View file

@ -0,0 +1,5 @@
package info.nightscout.androidaps.plugins.iob.iobCobCalculator.events
import info.nightscout.androidaps.events.Event
class EventBucketedDataCreated : Event()

View file

@ -137,12 +137,14 @@ open class DateUtil @Inject constructor(private val context: Context) {
return if (mills == 0L) "" else dateString(mills) + " " + timeStringWithSeconds(mills) return if (mills == 0L) "" else dateString(mills) + " " + timeStringWithSeconds(mills)
} }
fun minAgo(resourceHelper: ResourceHelper, time: Long): String { fun minAgo(resourceHelper: ResourceHelper, time: Long?): String {
if (time == null) return ""
val mins = ((now() - time) / 1000 / 60).toInt() val mins = ((now() - time) / 1000 / 60).toInt()
return resourceHelper.gs(R.string.minago, mins) return resourceHelper.gs(R.string.minago, mins)
} }
fun minAgoShort(time: Long): String { fun minAgoShort(time: Long?): String {
if (time == null) return ""
val mins = ((time - now()) / 1000 / 60).toInt() val mins = ((time - now()) / 1000 / 60).toInt()
return (if (mins > 0) "+" else "") + mins return (if (mins > 0) "+" else "") + mins
} }

View file

@ -7,8 +7,6 @@ import io.reactivex.Completable
import io.reactivex.Maybe import io.reactivex.Maybe
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.internal.operators.maybe.MaybeJust
import io.reactivex.rxkotlin.subscribeBy
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.PublishSubject
import java.util.concurrent.Callable import java.util.concurrent.Callable
@ -83,6 +81,11 @@ open class AppRepository @Inject internal constructor(
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.toWrappedSingle() .toWrappedSingle()
fun getLastGlucoseValueWrapped(): Single<ValueWrapper<GlucoseValue>> =
database.glucoseValueDao.getLast()
.subscribeOn(Schedulers.io())
.toWrappedSingle()
/* /*
* returns a Pair of the next entity to sync and the ID of the "update". * returns a Pair of the next entity to sync and the ID of the "update".
* The update id might either be the entry id itself if it is a new entry - or the id * The update id might either be the entry id itself if it is a new entry - or the id

View file

@ -2,6 +2,7 @@ package info.nightscout.androidaps.database
import android.content.Context import android.content.Context
import androidx.room.Room import androidx.room.Room
import androidx.room.RoomDatabase.Callback
import androidx.room.migration.Migration import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import dagger.Module import dagger.Module
@ -25,6 +26,15 @@ open class DatabaseModule {
// .addMigrations(migration6to7) // .addMigrations(migration6to7)
// .addMigrations(migration7to8) // .addMigrations(migration7to8)
// .addMigrations(migration11to12) // .addMigrations(migration11to12)
.addCallback(object : Callback() {
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
db.execSQL("CREATE INDEX IF NOT EXISTS `index_temporaryBasals_end` ON `temporaryBasals` (`timestamp` + `duration`)")
db.execSQL("CREATE INDEX IF NOT EXISTS `index_extendedBoluses_end` ON `extendedBoluses` (`timestamp` + `duration`)")
db.execSQL("CREATE INDEX IF NOT EXISTS `index_temporaryTargets_end` ON `temporaryTargets` (`timestamp` + `duration`)")
db.execSQL("CREATE INDEX IF NOT EXISTS `index_carbs_end` ON `carbs` (`timestamp` + `duration`)")
}
})
.fallbackToDestructiveMigration() .fallbackToDestructiveMigration()
.build() .build()

View file

@ -16,6 +16,9 @@ internal interface GlucoseValueDao : TraceableDao<GlucoseValue> {
@Query("DELETE FROM $TABLE_GLUCOSE_VALUES") @Query("DELETE FROM $TABLE_GLUCOSE_VALUES")
override fun deleteAllEntries() override fun deleteAllEntries()
@Query("SELECT * FROM $TABLE_GLUCOSE_VALUES ORDER BY id DESC limit 1")
fun getLast(): Maybe<GlucoseValue>
@Query("SELECT id FROM $TABLE_GLUCOSE_VALUES ORDER BY id DESC limit 1") @Query("SELECT id FROM $TABLE_GLUCOSE_VALUES ORDER BY id DESC limit 1")
fun getLastId(): Maybe<Long> fun getLastId(): Maybe<Long>

View file

@ -8,7 +8,7 @@ class InvalidateAAPSStartedTherapyEventTransaction(private val note: String) : T
val result = TransactionResult() val result = TransactionResult()
val therapyEvents = database.therapyEventDao.getValidByType(TherapyEvent.Type.NOTE) val therapyEvents = database.therapyEventDao.getValidByType(TherapyEvent.Type.NOTE)
for (event in therapyEvents) { for (event in therapyEvents) {
if (event.note?.contains(note) == true) { if (event.note?.contains(note) == true && event.isValid) {
event.isValid = false event.isValid = false
database.therapyEventDao.updateExistingEntry(event) database.therapyEventDao.updateExistingEntry(event)
result.invalidated.add(event) result.invalidated.add(event)