Implement smoothing over bucketed data

This commit is contained in:
Milos Kozak 2022-12-08 11:30:39 +01:00
parent 6d255032a8
commit a1def2a40f
40 changed files with 595 additions and 280 deletions

View file

@ -16,7 +16,9 @@ import info.nightscout.core.graph.data.ScaledDataPoint
import info.nightscout.database.entities.GlucoseValue import info.nightscout.database.entities.GlucoseValue
import info.nightscout.database.entities.TemporaryTarget import info.nightscout.database.entities.TemporaryTarget
import info.nightscout.interfaces.aps.AutosensData import info.nightscout.interfaces.aps.AutosensData
import info.nightscout.interfaces.aps.AutosensDataStore
import info.nightscout.interfaces.iob.CobInfo import info.nightscout.interfaces.iob.CobInfo
import info.nightscout.interfaces.iob.InMemoryGlucoseValue
import info.nightscout.interfaces.iob.IobCobCalculator import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.iob.IobTotal import info.nightscout.interfaces.iob.IobTotal
@ -45,12 +47,12 @@ interface OverviewData {
* BG * BG
*/ */
val lastBg: GlucoseValue? fun lastBg(autosensDataStore: AutosensDataStore): InMemoryGlucoseValue?
val isLow: Boolean fun isLow(autosensDataStore: AutosensDataStore): Boolean
val isHigh: Boolean fun isHigh(autosensDataStore: AutosensDataStore): Boolean
@ColorInt fun lastBgColor(context: Context?): Int @ColorInt fun lastBgColor(context: Context?, autosensDataStore: AutosensDataStore): Int
val lastBgDescription: String fun lastBgDescription(autosensDataStore: AutosensDataStore): String
val isActualBg: Boolean fun isActualBg(autosensDataStore: AutosensDataStore): Boolean
/* /*
* TEMPORARY BASAL * TEMPORARY BASAL
*/ */

View file

@ -1,6 +1,7 @@
package info.nightscout.core.graph.data package info.nightscout.core.graph.data
import android.content.Context import android.content.Context
import android.graphics.Paint
import info.nightscout.database.entities.Bolus import info.nightscout.database.entities.Bolus
import info.nightscout.interfaces.plugin.ActivePlugin import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.profile.DefaultValueHelper import info.nightscout.interfaces.profile.DefaultValueHelper
@ -22,7 +23,7 @@ class BolusDataPoint(
get() = DecimalFormatter.toPumpSupportedBolus(data.amount, activePlugin.activePump, rh) get() = DecimalFormatter.toPumpSupportedBolus(data.amount, activePlugin.activePump, rh)
override val duration = 0L override val duration = 0L
override val size = 2f override val size = 2f
override val paintStyle: Paint.Style = Paint.Style.FILL // not used
override val shape override val shape
get() = if (data.type == Bolus.Type.SMB) PointsWithLabelGraphSeries.Shape.SMB else PointsWithLabelGraphSeries.Shape.BOLUS get() = if (data.type == Bolus.Type.SMB) PointsWithLabelGraphSeries.Shape.SMB else PointsWithLabelGraphSeries.Shape.BOLUS

View file

@ -1,6 +1,7 @@
package info.nightscout.core.graph.data package info.nightscout.core.graph.data
import android.content.Context import android.content.Context
import android.graphics.Paint
import info.nightscout.core.graph.R import info.nightscout.core.graph.R
import info.nightscout.database.entities.Carbs import info.nightscout.database.entities.Carbs
import info.nightscout.shared.interfaces.ResourceHelper import info.nightscout.shared.interfaces.ResourceHelper
@ -18,6 +19,7 @@ class CarbsDataPoint(
override val duration = 0L override val duration = 0L
override val size = 2f override val size = 2f
override val shape = PointsWithLabelGraphSeries.Shape.CARBS override val shape = PointsWithLabelGraphSeries.Shape.CARBS
override val paintStyle: Paint.Style = Paint.Style.FILL // not used
override fun color(context: Context?): Int { override fun color(context: Context?): Int {
return if (data.isValid) rh.gac(context, info.nightscout.core.ui.R.attr.cobColor) else rh.gac(context, info.nightscout.core.ui.R.attr.alarmColor) return if (data.isValid) rh.gac(context, info.nightscout.core.ui.R.attr.cobColor) else rh.gac(context, info.nightscout.core.ui.R.attr.alarmColor)

View file

@ -1,6 +1,7 @@
package info.nightscout.core.graph.data package info.nightscout.core.graph.data
import android.content.Context import android.content.Context
import android.graphics.Paint
import com.jjoe64.graphview.series.DataPointInterface import com.jjoe64.graphview.series.DataPointInterface
interface DataPointWithLabelInterface : DataPointInterface { interface DataPointWithLabelInterface : DataPointInterface {
@ -13,5 +14,6 @@ interface DataPointWithLabelInterface : DataPointInterface {
val duration: Long val duration: Long
val shape: PointsWithLabelGraphSeries.Shape val shape: PointsWithLabelGraphSeries.Shape
val size: Float val size: Float
val paintStyle: Paint.Style
fun color(context: Context?): Int fun color(context: Context?): Int
} }

View file

@ -1,6 +1,7 @@
package info.nightscout.core.graph.data package info.nightscout.core.graph.data
import android.content.Context import android.content.Context
import android.graphics.Paint
import info.nightscout.database.entities.EffectiveProfileSwitch import info.nightscout.database.entities.EffectiveProfileSwitch
import info.nightscout.shared.interfaces.ResourceHelper import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.utils.T import info.nightscout.shared.utils.T
@ -22,6 +23,7 @@ class EffectiveProfileSwitchDataPoint(
override val duration = 0L override val duration = 0L
override val shape = PointsWithLabelGraphSeries.Shape.PROFILE override val shape = PointsWithLabelGraphSeries.Shape.PROFILE
override val size = 2f override val size = 2f
override val paintStyle: Paint.Style = Paint.Style.FILL // not used
override fun color(context: Context?): Int { override fun color(context: Context?): Int {
return rh.gac(context, info.nightscout.core.ui.R.attr.profileSwitchColor) return rh.gac(context, info.nightscout.core.ui.R.attr.profileSwitchColor)
} }

View file

@ -1,6 +1,7 @@
package info.nightscout.core.graph.data package info.nightscout.core.graph.data
import android.content.Context import android.content.Context
import android.graphics.Paint
import info.nightscout.database.entities.ExtendedBolus import info.nightscout.database.entities.ExtendedBolus
import info.nightscout.interfaces.utils.DecimalFormatter import info.nightscout.interfaces.utils.DecimalFormatter
import info.nightscout.shared.interfaces.ResourceHelper import info.nightscout.shared.interfaces.ResourceHelper
@ -18,6 +19,7 @@ class ExtendedBolusDataPoint(
override val duration get() = data.duration override val duration get() = data.duration
override val size = 10f override val size = 10f
override val shape = PointsWithLabelGraphSeries.Shape.EXTENDEDBOLUS override val shape = PointsWithLabelGraphSeries.Shape.EXTENDEDBOLUS
override val paintStyle: Paint.Style = Paint.Style.FILL // not used
override fun color(context: Context?): Int { override fun color(context: Context?): Int {
return rh.gac(context, info.nightscout.core.ui.R.attr.extBolusColor) return rh.gac(context, info.nightscout.core.ui.R.attr.extBolusColor)
} }

View file

@ -1,6 +1,7 @@
package info.nightscout.core.graph.data package info.nightscout.core.graph.data
import android.content.Context import android.content.Context
import android.graphics.Paint
import info.nightscout.database.entities.GlucoseValue import info.nightscout.database.entities.GlucoseValue
import info.nightscout.interfaces.Constants import info.nightscout.interfaces.Constants
import info.nightscout.interfaces.GlucoseUnit import info.nightscout.interfaces.GlucoseUnit
@ -16,7 +17,7 @@ class GlucoseValueDataPoint(
private val rh: ResourceHelper private val rh: ResourceHelper
) : DataPointWithLabelInterface { ) : DataPointWithLabelInterface {
fun valueToUnits(units: GlucoseUnit): Double = private fun valueToUnits(units: GlucoseUnit): Double =
if (units == GlucoseUnit.MGDL) data.value else data.value * Constants.MGDL_TO_MMOLL if (units == GlucoseUnit.MGDL) data.value else data.value * Constants.MGDL_TO_MMOLL
override fun getX(): Double = data.timestamp.toDouble() override fun getX(): Double = data.timestamp.toDouble()
@ -26,16 +27,13 @@ class GlucoseValueDataPoint(
override val label: String = Profile.toCurrentUnitsString(profileFunction, data.value) override val label: String = Profile.toCurrentUnitsString(profileFunction, data.value)
override val duration = 0L override val duration = 0L
override val shape get() = if (isPrediction) PointsWithLabelGraphSeries.Shape.PREDICTION else PointsWithLabelGraphSeries.Shape.BG override val shape get() = if (isPrediction) PointsWithLabelGraphSeries.Shape.PREDICTION else PointsWithLabelGraphSeries.Shape.BG
override val size = 1f override val size = if (isPrediction) 1f else 0.6f
override val paintStyle: Paint.Style = if (isPrediction) Paint.Style.FILL else Paint.Style.STROKE
override fun color(context: Context?): Int { override fun color(context: Context?): Int {
val units = profileFunction.getUnits()
val lowLine = defaultValueHelper.determineLowLine()
val highLine = defaultValueHelper.determineHighLine()
return when { return when {
isPrediction -> predictionColor(context) isPrediction -> predictionColor(context)
valueToUnits(units) < lowLine -> rh.gac(context, info.nightscout.core.ui.R.attr.bgLow) else -> rh.gac(context, info.nightscout.core.ui.R.attr.originalBgValueColor)
valueToUnits(units) > highLine -> rh.gac(context, info.nightscout.core.ui.R.attr.highColor)
else -> rh.gac(context, info.nightscout.core.ui.R.attr.bgInRange)
} }
} }

View file

@ -1,20 +1,23 @@
package info.nightscout.core.graph.data package info.nightscout.core.graph.data
import android.content.Context import android.content.Context
import android.graphics.Paint
import info.nightscout.interfaces.Constants import info.nightscout.interfaces.Constants
import info.nightscout.interfaces.GlucoseUnit import info.nightscout.interfaces.GlucoseUnit
import info.nightscout.interfaces.iob.InMemoryGlucoseValue import info.nightscout.interfaces.iob.InMemoryGlucoseValue
import info.nightscout.interfaces.profile.DefaultValueHelper
import info.nightscout.interfaces.profile.ProfileFunction import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.shared.interfaces.ResourceHelper import info.nightscout.shared.interfaces.ResourceHelper
class InMemoryGlucoseValueDataPoint( class InMemoryGlucoseValueDataPoint(
val data: InMemoryGlucoseValue, val data: InMemoryGlucoseValue,
private val defaultValueHelper: DefaultValueHelper,
private val profileFunction: ProfileFunction, private val profileFunction: ProfileFunction,
private val rh: ResourceHelper private val rh: ResourceHelper
) : DataPointWithLabelInterface { ) : DataPointWithLabelInterface {
fun valueToUnits(units: GlucoseUnit): Double = private fun valueToUnits(units: GlucoseUnit): Double =
if (units == GlucoseUnit.MGDL) data.value else data.value * Constants.MGDL_TO_MMOLL if (units == GlucoseUnit.MGDL) data.recalculated else data.recalculated * Constants.MGDL_TO_MMOLL
override fun getX(): Double = data.timestamp.toDouble() override fun getX(): Double = data.timestamp.toDouble()
override fun getY(): Double = valueToUnits(profileFunction.getUnits()) override fun getY(): Double = valueToUnits(profileFunction.getUnits())
@ -22,8 +25,18 @@ class InMemoryGlucoseValueDataPoint(
override val label: String = "" override val label: String = ""
override val duration = 0L override val duration = 0L
override val shape = PointsWithLabelGraphSeries.Shape.BUCKETED_BG override val shape = PointsWithLabelGraphSeries.Shape.BUCKETED_BG
override val size = 0.3f override val size = 1f
override val paintStyle: Paint.Style = Paint.Style.FILL
override fun color(context: Context?): Int { override fun color(context: Context?): Int {
return rh.gac(context, info.nightscout.core.ui.R.attr.inMemoryColor) val units = profileFunction.getUnits()
val lowLine = defaultValueHelper.determineLowLine()
val highLine = defaultValueHelper.determineHighLine()
return when {
valueToUnits(units) < lowLine -> rh.gac(context, info.nightscout.core.ui.R.attr.bgLow)
valueToUnits(units) > highLine -> rh.gac(context, info.nightscout.core.ui.R.attr.highColor)
else -> rh.gac(context, info.nightscout.core.ui.R.attr.bgInRange)
}
} }
} }

View file

@ -179,20 +179,20 @@ public class PointsWithLabelGraphSeries<E extends DataPointWithLabelInterface> e
// draw data point // draw data point
if (!overdraw) { if (!overdraw) {
if (value.getShape() == Shape.BG || value.getShape() == Shape.COB_FAIL_OVER) { if (value.getShape() == Shape.BG || value.getShape() == Shape.COB_FAIL_OVER) {
mPaint.setStyle(Paint.Style.FILL); mPaint.setStyle(value.getPaintStyle());
mPaint.setStrokeWidth(0); mPaint.setStrokeWidth(0);
canvas.drawCircle(endX, endY, value.getSize() * scaledPxSize, mPaint); canvas.drawCircle(endX, endY, value.getSize() * scaledPxSize, mPaint);
} else if (value.getShape() == Shape.BG || value.getShape() == Shape.IOB_PREDICTION || value.getShape() == Shape.BUCKETED_BG) { } else if (value.getShape() == Shape.BG || value.getShape() == Shape.IOB_PREDICTION || value.getShape() == Shape.BUCKETED_BG) {
mPaint.setColor(value.color(graphView.getContext())); mPaint.setColor(value.color(graphView.getContext()));
mPaint.setStyle(Paint.Style.FILL); mPaint.setStyle(value.getPaintStyle());
mPaint.setStrokeWidth(0); mPaint.setStrokeWidth(0);
canvas.drawCircle(endX, endY, value.getSize() * scaledPxSize, mPaint); canvas.drawCircle(endX, endY, value.getSize() * scaledPxSize, mPaint);
} else if (value.getShape() == Shape.PREDICTION) { } else if (value.getShape() == Shape.PREDICTION) {
mPaint.setColor(value.color(graphView.getContext())); mPaint.setColor(value.color(graphView.getContext()));
mPaint.setStyle(Paint.Style.FILL); mPaint.setStyle(value.getPaintStyle());
mPaint.setStrokeWidth(0); mPaint.setStrokeWidth(0);
canvas.drawCircle(endX, endY, scaledPxSize, mPaint); canvas.drawCircle(endX, endY, scaledPxSize, mPaint);
mPaint.setStyle(Paint.Style.FILL); mPaint.setStyle(value.getPaintStyle());
mPaint.setStrokeWidth(0); mPaint.setStrokeWidth(0);
canvas.drawCircle(endX, endY, scaledPxSize / 3, mPaint); canvas.drawCircle(endX, endY, scaledPxSize / 3, mPaint);
} else if (value.getShape() == Shape.RECTANGLE) { } else if (value.getShape() == Shape.RECTANGLE) {

View file

@ -1,6 +1,7 @@
package info.nightscout.core.graph.data package info.nightscout.core.graph.data
import android.content.Context import android.content.Context
import android.graphics.Paint
import info.nightscout.database.entities.TherapyEvent import info.nightscout.database.entities.TherapyEvent
import info.nightscout.interfaces.Constants import info.nightscout.interfaces.Constants
import info.nightscout.interfaces.Translator import info.nightscout.interfaces.Translator
@ -55,6 +56,7 @@ class TherapyEventDataPoint(
duration > 0 -> PointsWithLabelGraphSeries.Shape.GENERAL_WITH_DURATION duration > 0 -> PointsWithLabelGraphSeries.Shape.GENERAL_WITH_DURATION
else -> PointsWithLabelGraphSeries.Shape.GENERAL else -> PointsWithLabelGraphSeries.Shape.GENERAL
} }
override val paintStyle: Paint.Style = Paint.Style.FILL // not used
override val size get() = if (rh.gb(info.nightscout.shared.R.bool.isTablet)) 12.0f else 10.0f override val size get() = if (rh.gb(info.nightscout.shared.R.bool.isTablet)) 12.0f else 10.0f
override fun color(context: Context?): Int { override fun color(context: Context?): Int {

View file

@ -2,8 +2,9 @@ package info.nightscout.interfaces.iob
import info.nightscout.database.entities.GlucoseValue import info.nightscout.database.entities.GlucoseValue
class InMemoryGlucoseValue constructor(var timestamp: Long = 0L, var value: Double = 0.0, var interpolated: Boolean = false) { class InMemoryGlucoseValue constructor(var timestamp: Long = 0L, var value: Double = 0.0, var trendArrow: GlucoseValue.TrendArrow = GlucoseValue.TrendArrow.NONE, var smoothed: Double? = null) {
constructor(gv: GlucoseValue) : this(gv.timestamp, gv.value) constructor(gv: GlucoseValue) : this(gv.timestamp, gv.value, gv.trendArrow)
// var generated : value doesn't correspond to real value with timestamp close to real BG
val recalculated: Double get() = smoothed ?: value
} }

View file

@ -78,7 +78,7 @@ interface ActivePlugin {
/** /**
* Smoothing plugin * Smoothing plugin
*/ */
val activeSmoothing: Smoothing? val activeSmoothing: Smoothing
/** /**
* Currently selected NsClient plugin * Currently selected NsClient plugin

View file

@ -1,4 +1,16 @@
package info.nightscout.interfaces.smoothing package info.nightscout.interfaces.smoothing
import info.nightscout.interfaces.iob.InMemoryGlucoseValue
interface Smoothing { interface Smoothing {
/**
* Smooth values in List
*
* @param data input glucose values ([0] to be the most recent one)
* @param updateWindow amount of values to the past to smooth
*
* @return new List with smoothed values (smoothed values are stored in [InMemoryGlucoseValue.smoothed])
*/
fun smooth(data: MutableList<InMemoryGlucoseValue>): MutableList<InMemoryGlucoseValue>
} }

View file

@ -1,6 +1,7 @@
package info.nightscout.interfaces.utils package info.nightscout.interfaces.utils
import info.nightscout.database.entities.GlucoseValue import info.nightscout.database.entities.GlucoseValue
import info.nightscout.interfaces.aps.AutosensDataStore
/** /**
* Convert BG direction value to trend arrow or calculate it if not provided * Convert BG direction value to trend arrow or calculate it if not provided
@ -15,6 +16,13 @@ interface TrendCalculator {
* @return TrendArrow * @return TrendArrow
*/ */
fun getTrendArrow(glucoseValue: GlucoseValue?): GlucoseValue.TrendArrow fun getTrendArrow(glucoseValue: GlucoseValue?): GlucoseValue.TrendArrow
/**
* Provide or calculate trend from newest bucketed data
*
* @param autosensDataStore current store from IobCobCalculator
* @return TrendArrow
*/
fun getTrendArrow(autosensDataStore: AutosensDataStore): GlucoseValue.TrendArrow?
/** /**
* Provide or calculate trend * Provide or calculate trend
@ -23,4 +31,11 @@ interface TrendCalculator {
* @return string description of TrendArrow * @return string description of TrendArrow
*/ */
fun getTrendDescription(glucoseValue: GlucoseValue?): String fun getTrendDescription(glucoseValue: GlucoseValue?): String
/**
* Provide or calculate trend from newest bucketed data
*
* @param autosensDataStore current store from IobCobCalculator
* @return string description of TrendArrow
*/
fun getTrendDescription(autosensDataStore: AutosensDataStore): String
} }

View file

@ -4,6 +4,7 @@ package info.nightscout.core.extensions
import info.nightscout.database.entities.GlucoseValue import info.nightscout.database.entities.GlucoseValue
import info.nightscout.interfaces.Constants import info.nightscout.interfaces.Constants
import info.nightscout.interfaces.GlucoseUnit import info.nightscout.interfaces.GlucoseUnit
import info.nightscout.interfaces.iob.InMemoryGlucoseValue
import info.nightscout.interfaces.utils.DecimalFormatter import info.nightscout.interfaces.utils.DecimalFormatter
import info.nightscout.shared.utils.DateUtil import info.nightscout.shared.utils.DateUtil
import org.json.JSONObject import org.json.JSONObject
@ -26,3 +27,12 @@ fun GlucoseValue.toJson(isAdd : Boolean, dateUtil: DateUtil): JSONObject =
.put("direction", trendArrow.text) .put("direction", trendArrow.text)
.put("type", "sgv") .put("type", "sgv")
.also { if (isAdd && interfaceIDs.nightscoutId != null) it.put("_id", interfaceIDs.nightscoutId) } .also { if (isAdd && interfaceIDs.nightscoutId != null) it.put("_id", interfaceIDs.nightscoutId) }
fun InMemoryGlucoseValue.valueToUnits(units: GlucoseUnit): Double =
if (units == GlucoseUnit.MGDL) recalculated
else recalculated * Constants.MGDL_TO_MMOLL
fun InMemoryGlucoseValue.valueToUnitsString(units: GlucoseUnit): String =
if (units == GlucoseUnit.MGDL) DecimalFormatter.to0Decimal(recalculated)
else DecimalFormatter.to1Decimal(recalculated * Constants.MGDL_TO_MMOLL)

View file

@ -206,7 +206,7 @@
<item name="smbColor">@color/tempbasal</item> <item name="smbColor">@color/tempbasal</item>
<item name="bolusDataPointColor">@color/pumpHistory</item> <item name="bolusDataPointColor">@color/pumpHistory</item>
<item name="profileSwitchColor">@color/profileSwitch</item> <item name="profileSwitchColor">@color/profileSwitch</item>
<item name="inMemoryColor">@color/white</item> <item name="originalBgValueColor">@color/white</item>
<item name="therapyEvent_NS_MBG">@color/red</item> <item name="therapyEvent_NS_MBG">@color/red</item>
<item name="therapyEvent_FINGER_STICK_BG_VALUE">@color/red</item> <item name="therapyEvent_FINGER_STICK_BG_VALUE">@color/red</item>
<item name="therapyEvent_EXERCISE">@color/blue</item> <item name="therapyEvent_EXERCISE">@color/blue</item>

View file

@ -180,7 +180,7 @@
<attr name="smbColor" format="reference|color" /> <attr name="smbColor" format="reference|color" />
<attr name="bolusDataPointColor" format="reference|color" /> <attr name="bolusDataPointColor" format="reference|color" />
<attr name="profileSwitchColor" format="reference|color" /> <attr name="profileSwitchColor" format="reference|color" />
<attr name="inMemoryColor" format="reference|color" /> <attr name="originalBgValueColor" format="reference|color" />
<attr name="therapyEvent_NS_MBG" format="reference|color" /> <attr name="therapyEvent_NS_MBG" format="reference|color" />
<attr name="therapyEvent_FINGER_STICK_BG_VALUE" format="reference|color" /> <attr name="therapyEvent_FINGER_STICK_BG_VALUE" format="reference|color" />
<attr name="therapyEvent_EXERCISE" format="reference|color" /> <attr name="therapyEvent_EXERCISE" format="reference|color" />

View file

@ -209,7 +209,7 @@
<item name="smbColor">@color/tempbasal</item> <item name="smbColor">@color/tempbasal</item>
<item name="bolusDataPointColor">@color/pumpHistory</item> <item name="bolusDataPointColor">@color/pumpHistory</item>
<item name="profileSwitchColor">@color/profileSwitch</item> <item name="profileSwitchColor">@color/profileSwitch</item>
<item name="inMemoryColor">@color/white</item> <item name="originalBgValueColor">@color/white</item>
<item name="therapyEvent_NS_MBG">@color/red</item> <item name="therapyEvent_NS_MBG">@color/red</item>
<item name="therapyEvent_FINGER_STICK_BG_VALUE">@color/red</item> <item name="therapyEvent_FINGER_STICK_BG_VALUE">@color/red</item>
<item name="therapyEvent_EXERCISE">@color/blue</item> <item name="therapyEvent_EXERCISE">@color/blue</item>

View file

@ -2,6 +2,8 @@ package info.nightscout.implementation
import info.nightscout.database.entities.GlucoseValue import info.nightscout.database.entities.GlucoseValue
import info.nightscout.database.impl.AppRepository import info.nightscout.database.impl.AppRepository
import info.nightscout.interfaces.aps.AutosensDataStore
import info.nightscout.interfaces.iob.InMemoryGlucoseValue
import info.nightscout.interfaces.utils.TrendCalculator import info.nightscout.interfaces.utils.TrendCalculator
import info.nightscout.shared.interfaces.ResourceHelper import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.utils.T import info.nightscout.shared.utils.T
@ -62,4 +64,55 @@ class TrendCalculatorImpl @Inject constructor(
else -> GlucoseValue.TrendArrow.NONE else -> GlucoseValue.TrendArrow.NONE
} }
} }
override fun getTrendArrow(autosensDataStore: AutosensDataStore): GlucoseValue.TrendArrow? {
val data = autosensDataStore.getBucketedDataTableCopy() ?: return null
if (data.size == 0) return null
val glucoseValue = data[0]
return when {
glucoseValue.value != glucoseValue.recalculated -> calculateDirection(data) // always recalculate after smoothing
glucoseValue.trendArrow != GlucoseValue.TrendArrow.NONE -> glucoseValue.trendArrow
else -> calculateDirection(data)
}
}
override fun getTrendDescription(autosensDataStore: AutosensDataStore): String {
return when (getTrendArrow(autosensDataStore)) {
GlucoseValue.TrendArrow.DOUBLE_DOWN -> rh.gs(info.nightscout.core.ui.R.string.a11y_arrow_double_down)
GlucoseValue.TrendArrow.SINGLE_DOWN -> rh.gs(info.nightscout.core.ui.R.string.a11y_arrow_single_down)
GlucoseValue.TrendArrow.FORTY_FIVE_DOWN -> rh.gs(info.nightscout.core.ui.R.string.a11y_arrow_forty_five_down)
GlucoseValue.TrendArrow.FLAT -> rh.gs(info.nightscout.core.ui.R.string.a11y_arrow_flat)
GlucoseValue.TrendArrow.FORTY_FIVE_UP -> rh.gs(info.nightscout.core.ui.R.string.a11y_arrow_forty_five_up)
GlucoseValue.TrendArrow.SINGLE_UP -> rh.gs(info.nightscout.core.ui.R.string.a11y_arrow_single_up)
GlucoseValue.TrendArrow.DOUBLE_UP -> rh.gs(info.nightscout.core.ui.R.string.a11y_arrow_double_up)
GlucoseValue.TrendArrow.NONE -> rh.gs(info.nightscout.core.ui.R.string.a11y_arrow_none)
else -> rh.gs(info.nightscout.core.ui.R.string.a11y_arrow_unknown)
}
}
private fun calculateDirection(readings: MutableList<InMemoryGlucoseValue>): GlucoseValue.TrendArrow {
if (readings.size < 2)
return GlucoseValue.TrendArrow.NONE
val current = readings[0]
val previous = readings[1]
// Avoid division by 0
val slope =
if (current.timestamp == previous.timestamp) 0.0
else (previous.recalculated - current.recalculated) / (previous.timestamp - current.timestamp)
val slopeByMinute = slope * 60000
return when {
slopeByMinute <= -3.5 -> GlucoseValue.TrendArrow.DOUBLE_DOWN
slopeByMinute <= -2 -> GlucoseValue.TrendArrow.SINGLE_DOWN
slopeByMinute <= -1 -> GlucoseValue.TrendArrow.FORTY_FIVE_DOWN
slopeByMinute <= 1 -> GlucoseValue.TrendArrow.FLAT
slopeByMinute <= 2 -> GlucoseValue.TrendArrow.FORTY_FIVE_UP
slopeByMinute <= 3.5 -> GlucoseValue.TrendArrow.SINGLE_UP
slopeByMinute <= 40 -> GlucoseValue.TrendArrow.DOUBLE_UP
else -> GlucoseValue.TrendArrow.NONE
}
}
} }

View file

@ -25,7 +25,7 @@ class GlucoseStatusProviderImpl @Inject constructor(
get() = getGlucoseStatusData() get() = getGlucoseStatusData()
override fun getGlucoseStatusData(allowOldData: Boolean): GlucoseStatus? { override fun getGlucoseStatusData(allowOldData: Boolean): GlucoseStatus? {
val data = iobCobCalculator.ads.getBgReadingsDataTableCopy() val data = iobCobCalculator.ads.getBucketedDataTableCopy() ?: return null
val sizeRecords = data.size val sizeRecords = data.size
if (sizeRecords == 0) { if (sizeRecords == 0) {
aapsLogger.debug(LTag.GLUCOSE, "sizeRecords==0") aapsLogger.debug(LTag.GLUCOSE, "sizeRecords==0")
@ -41,7 +41,7 @@ class GlucoseStatusProviderImpl @Inject constructor(
if (sizeRecords == 1) { if (sizeRecords == 1) {
aapsLogger.debug(LTag.GLUCOSE, "sizeRecords==1") aapsLogger.debug(LTag.GLUCOSE, "sizeRecords==1")
return GlucoseStatus( return GlucoseStatus(
glucose = now.value, glucose = now.recalculated,
noise = 0.0, noise = 0.0,
delta = 0.0, delta = 0.0,
shortAvgDelta = 0.0, shortAvgDelta = 0.0,
@ -49,31 +49,30 @@ class GlucoseStatusProviderImpl @Inject constructor(
date = nowDate date = nowDate
).asRounded() ).asRounded()
} }
val nowValueList = ArrayList<Double>()
val lastDeltas = ArrayList<Double>() val lastDeltas = ArrayList<Double>()
val shortDeltas = ArrayList<Double>() val shortDeltas = ArrayList<Double>()
val longDeltas = ArrayList<Double>() val longDeltas = ArrayList<Double>()
// Use the latest sgv value in the now calculations // Use the latest sgv value in the now calculations
nowValueList.add(now.value)
for (i in 1 until sizeRecords) { for (i in 1 until sizeRecords) {
if (data[i].value > 38) { if (data[i].recalculated > 38) {
val then = data[i] val then = data[i]
val thenDate = then.timestamp val thenDate = then.timestamp
val minutesAgo = ((nowDate - thenDate) / (1000.0 * 60)).roundToLong() val minutesAgo = ((nowDate - thenDate) / (1000.0 * 60)).roundToLong()
// multiply by 5 to get the same units as delta, i.e. mg/dL/5m // multiply by 5 to get the same units as delta, i.e. mg/dL/5m
change = now.value - then.value change = now.recalculated - then.recalculated
val avgDel = change / minutesAgo * 5 val avgDel = change / minutesAgo * 5
aapsLogger.debug(LTag.GLUCOSE, "$then minutesAgo=$minutesAgo avgDelta=$avgDel") aapsLogger.debug(LTag.GLUCOSE, "$then minutesAgo=$minutesAgo avgDelta=$avgDel")
// use the average of all data points in the last 2.5m for all further "now" calculations // use the average of all data points in the last 2.5m for all further "now" calculations
if (0 < minutesAgo && minutesAgo < 2.5) { // if (0 < minutesAgo && minutesAgo < 2.5) {
// Keep and average all values within the last 2.5 minutes // // Keep and average all values within the last 2.5 minutes
nowValueList.add(then.value) // nowValueList.add(then.recalculated)
now.value = average(nowValueList) // now.value = average(nowValueList)
// short_deltas are calculated from everything ~5-15 minutes ago // // short_deltas are calculated from everything ~5-15 minutes ago
} else if (2.5 < minutesAgo && minutesAgo < 17.5) { // } else
if (2.5 < minutesAgo && minutesAgo < 17.5) {
//console.error(minutesAgo, avgDelta); //console.error(minutesAgo, avgDelta);
shortDeltas.add(avgDel) shortDeltas.add(avgDel)
// last_deltas are calculated from everything ~5 minutes ago // last_deltas are calculated from everything ~5 minutes ago
@ -96,7 +95,7 @@ class GlucoseStatusProviderImpl @Inject constructor(
average(lastDeltas) average(lastDeltas)
} }
return GlucoseStatus( return GlucoseStatus(
glucose = now.value, glucose = now.recalculated,
date = nowDate, date = nowDate,
noise = 0.0, //for now set to nothing as not all CGMs report noise noise = 0.0, //for now set to nothing as not all CGMs report noise
shortAvgDelta = shortAverageDelta, shortAvgDelta = shortAverageDelta,
@ -105,6 +104,89 @@ class GlucoseStatusProviderImpl @Inject constructor(
).also { aapsLogger.debug(LTag.GLUCOSE, it.log()) }.asRounded() ).also { aapsLogger.debug(LTag.GLUCOSE, it.log()) }.asRounded()
} }
/* Real BG (previous) version
override fun getGlucoseStatusData(allowOldData: Boolean): GlucoseStatus? {
val data = iobCobCalculator.ads.getBgReadingsDataTableCopy()
val sizeRecords = data.size
if (sizeRecords == 0) {
aapsLogger.debug(LTag.GLUCOSE, "sizeRecords==0")
return null
}
if (data[0].timestamp < dateUtil.now() - 7 * 60 * 1000L && !allowOldData) {
aapsLogger.debug(LTag.GLUCOSE, "oldData")
return null
}
val now = data[0]
val nowDate = now.timestamp
var change: Double
if (sizeRecords == 1) {
aapsLogger.debug(LTag.GLUCOSE, "sizeRecords==1")
return GlucoseStatus(
glucose = now.value,
noise = 0.0,
delta = 0.0,
shortAvgDelta = 0.0,
longAvgDelta = 0.0,
date = nowDate
).asRounded()
}
val nowValueList = ArrayList<Double>()
val lastDeltas = ArrayList<Double>()
val shortDeltas = ArrayList<Double>()
val longDeltas = ArrayList<Double>()
// Use the latest sgv value in the now calculations
nowValueList.add(now.value)
for (i in 1 until sizeRecords) {
if (data[i].value > 38) {
val then = data[i]
val thenDate = then.timestamp
val minutesAgo = ((nowDate - thenDate) / (1000.0 * 60)).roundToLong()
// multiply by 5 to get the same units as delta, i.e. mg/dL/5m
change = now.value - then.value
val avgDel = change / minutesAgo * 5
aapsLogger.debug(LTag.GLUCOSE, "$then minutesAgo=$minutesAgo avgDelta=$avgDel")
// use the average of all data points in the last 2.5m for all further "now" calculations
if (0 < minutesAgo && minutesAgo < 2.5) {
// Keep and average all values within the last 2.5 minutes
nowValueList.add(then.value)
now.value = average(nowValueList)
// short_deltas are calculated from everything ~5-15 minutes ago
} else if (2.5 < minutesAgo && minutesAgo < 17.5) {
//console.error(minutesAgo, avgDelta);
shortDeltas.add(avgDel)
// last_deltas are calculated from everything ~5 minutes ago
if (2.5 < minutesAgo && minutesAgo < 7.5) {
lastDeltas.add(avgDel)
}
// long_deltas are calculated from everything ~20-40 minutes ago
} else if (17.5 < minutesAgo && minutesAgo < 42.5) {
longDeltas.add(avgDel)
} else {
// Do not process any more records after >= 42.5 minutes
break
}
}
}
val shortAverageDelta = average(shortDeltas)
val delta = if (lastDeltas.isEmpty()) {
shortAverageDelta
} else {
average(lastDeltas)
}
return GlucoseStatus(
glucose = now.value,
date = nowDate,
noise = 0.0, //for now set to nothing as not all CGMs report noise
shortAvgDelta = shortAverageDelta,
delta = delta,
longAvgDelta = average(longDeltas),
).also { aapsLogger.debug(LTag.GLUCOSE, it.log()) }.asRounded()
}
*/
companion object { companion object {
fun average(array: ArrayList<Double>): Double { fun average(array: ArrayList<Double>): Double {

View file

@ -26,7 +26,9 @@ import info.nightscout.database.entities.GlucoseValue
import info.nightscout.database.entities.TemporaryTarget import info.nightscout.database.entities.TemporaryTarget
import info.nightscout.database.impl.AppRepository import info.nightscout.database.impl.AppRepository
import info.nightscout.interfaces.aps.AutosensData import info.nightscout.interfaces.aps.AutosensData
import info.nightscout.interfaces.aps.AutosensDataStore
import info.nightscout.interfaces.iob.CobInfo import info.nightscout.interfaces.iob.CobInfo
import info.nightscout.interfaces.iob.InMemoryGlucoseValue
import info.nightscout.interfaces.iob.IobCobCalculator import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.iob.IobTotal import info.nightscout.interfaces.iob.IobTotal
import info.nightscout.interfaces.plugin.ActivePlugin import info.nightscout.interfaces.plugin.ActivePlugin
@ -119,43 +121,42 @@ class OverviewDataImpl @Inject constructor(
* BG * BG
*/ */
override val lastBg: GlucoseValue? override fun lastBg(autosensDataStore: AutosensDataStore): InMemoryGlucoseValue? =
get() = autosensDataStore.bucketedData?.let { if (it.size > 0) it[0] else null }
repository.getLastGlucoseValueWrapped().blockingGet().let { gvWrapped -> // repository.getLastGlucoseValueWrapped().blockingGet().let { gvWrapped ->
if (gvWrapped is ValueWrapper.Existing) gvWrapped.value // if (gvWrapped is ValueWrapper.Existing) gvWrapped.value
else null // else null
} // }
override val isLow: Boolean override fun isLow(autosensDataStore: AutosensDataStore): Boolean =
get() = lastBg?.let { lastBg -> lastBg(autosensDataStore)?.let { lastBg ->
lastBg.valueToUnits(profileFunction.getUnits()) < defaultValueHelper.determineLowLine() lastBg.valueToUnits(profileFunction.getUnits()) < defaultValueHelper.determineLowLine()
} ?: false } ?: false
override val isHigh: Boolean override fun isHigh(autosensDataStore: AutosensDataStore): Boolean =
get() = lastBg?.let { lastBg -> lastBg(autosensDataStore)?.let { lastBg ->
lastBg.valueToUnits(profileFunction.getUnits()) > defaultValueHelper.determineHighLine() lastBg.valueToUnits(profileFunction.getUnits()) > defaultValueHelper.determineHighLine()
} ?: false } ?: false
@ColorInt @ColorInt
override fun lastBgColor(context: Context?): Int = override fun lastBgColor(context: Context?, autosensDataStore: AutosensDataStore): Int =
when { when {
isLow -> rh.gac(context, info.nightscout.core.ui.R.attr.bgLow) isLow(autosensDataStore) -> rh.gac(context, info.nightscout.core.ui.R.attr.bgLow)
isHigh -> rh.gac(context, info.nightscout.core.ui.R.attr.highColor) isHigh(autosensDataStore) -> rh.gac(context, info.nightscout.core.ui.R.attr.highColor)
else -> rh.gac(context, info.nightscout.core.ui.R.attr.bgInRange) else -> rh.gac(context, info.nightscout.core.ui.R.attr.bgInRange)
} }
override val lastBgDescription: String override fun lastBgDescription(autosensDataStore: AutosensDataStore): String =
get() = when { when {
isLow -> rh.gs(info.nightscout.core.ui.R.string.a11y_low) isLow(autosensDataStore) -> rh.gs(info.nightscout.core.ui.R.string.a11y_low)
isHigh -> rh.gs(info.nightscout.core.ui.R.string.a11y_high) isHigh(autosensDataStore) -> rh.gs(info.nightscout.core.ui.R.string.a11y_high)
else -> rh.gs(info.nightscout.core.ui.R.string.a11y_inrange) else -> rh.gs(info.nightscout.core.ui.R.string.a11y_inrange)
} }
override val isActualBg: Boolean override fun isActualBg(autosensDataStore: AutosensDataStore): Boolean =
get() = lastBg(autosensDataStore)?.let { lastBg ->
lastBg?.let { lastBg -> lastBg.timestamp > dateUtil.now() - T.mins(9).msecs()
lastBg.timestamp > dateUtil.now() - T.mins(9).msecs() } ?: false
} ?: false
/* /*
* TEMPORARY BASAL * TEMPORARY BASAL

View file

@ -37,6 +37,7 @@ class PluginStore @Inject constructor(
private var activeAPSStore: APS? = null private var activeAPSStore: APS? = null
private var activeInsulinStore: Insulin? = null private var activeInsulinStore: Insulin? = null
private var activeSensitivityStore: Sensitivity? = null private var activeSensitivityStore: Sensitivity? = null
private var activeSmoothingStore: Smoothing? = null
override fun loadDefaults() { override fun loadDefaults() {
verifySelectionInCategories() verifySelectionInCategories()
@ -107,6 +108,16 @@ class PluginStore @Inject constructor(
} }
setFragmentVisibilities((activeSensitivityStore as PluginBase).name, pluginsInCategory, PluginType.SENSITIVITY) setFragmentVisibilities((activeSensitivityStore as PluginBase).name, pluginsInCategory, PluginType.SENSITIVITY)
// PluginType.SMOOTHING
pluginsInCategory = getSpecificPluginsList(PluginType.SMOOTHING)
activeSmoothingStore = getTheOneEnabledInArray(pluginsInCategory, PluginType.SMOOTHING) as Smoothing?
if (activeSmoothingStore == null) {
activeSmoothingStore = getDefaultPlugin(PluginType.SMOOTHING) as Smoothing
(activeSmoothingStore as PluginBase).setPluginEnabled(PluginType.SMOOTHING, true)
aapsLogger.debug(LTag.CONFIGBUILDER, "Defaulting SmoothingInterface")
}
setFragmentVisibilities((activeSmoothingStore as PluginBase).name, pluginsInCategory, PluginType.SMOOTHING)
// PluginType.PROFILE // PluginType.PROFILE
pluginsInCategory = getSpecificPluginsList(PluginType.PROFILE) pluginsInCategory = getSpecificPluginsList(PluginType.PROFILE)
activeProfile = getTheOneEnabledInArray(pluginsInCategory, PluginType.PROFILE) as ProfileSource? activeProfile = getTheOneEnabledInArray(pluginsInCategory, PluginType.PROFILE) as ProfileSource?
@ -182,6 +193,10 @@ class PluginStore @Inject constructor(
get() = activeSensitivityStore get() = activeSensitivityStore
?: checkNotNull(activeSensitivityStore) { "No sensitivity selected" } ?: checkNotNull(activeSensitivityStore) { "No sensitivity selected" }
override val activeSmoothing: Smoothing
get() = activeSmoothingStore
?: checkNotNull(activeSmoothingStore) { "No smoothing selected" }
override val activeOverview: Overview override val activeOverview: Overview
get() = getSpecificPluginsListByInterface(Overview::class.java).first() as Overview get() = getSpecificPluginsListByInterface(Overview::class.java).first() as Overview
@ -191,10 +206,7 @@ class PluginStore @Inject constructor(
override val activeIobCobCalculator: IobCobCalculator override val activeIobCobCalculator: IobCobCalculator
get() = getSpecificPluginsListByInterface(IobCobCalculator::class.java).first() as IobCobCalculator get() = getSpecificPluginsListByInterface(IobCobCalculator::class.java).first() as IobCobCalculator
override val activeObjectives: Objectives? override val activeObjectives: Objectives?
get() = getSpecificPluginsListByInterface(Objectives::class.java).firstOrNull() as Objectives get() = getSpecificPluginsListByInterface(Objectives::class.java).firstOrNull() as Objectives?
override val activeSmoothing: Smoothing?
get() = getSpecificPluginsListByInterface(Smoothing::class.java).firstOrNull() as Smoothing
override val activeNsClient: NsClient? override val activeNsClient: NsClient?
get() = getTheOneEnabledInArray(getSpecificPluginsListByInterface(NsClient::class.java), PluginType.SYNC) as NsClient? get() = getTheOneEnabledInArray(getSpecificPluginsListByInterface(NsClient::class.java), PluginType.SYNC) as NsClient?

View file

@ -6,6 +6,7 @@ import info.nightscout.core.iob.log
import info.nightscout.database.entities.GlucoseValue import info.nightscout.database.entities.GlucoseValue
import info.nightscout.interfaces.aps.AutosensDataStore import info.nightscout.interfaces.aps.AutosensDataStore
import info.nightscout.interfaces.iob.GlucoseStatus import info.nightscout.interfaces.iob.GlucoseStatus
import info.nightscout.interfaces.iob.InMemoryGlucoseValue
import info.nightscout.interfaces.iob.IobCobCalculator import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.shared.utils.DateUtil import info.nightscout.shared.utils.DateUtil
import info.nightscout.shared.utils.T import info.nightscout.shared.utils.T
@ -40,7 +41,7 @@ class GlucoseStatusTest : TestBase() {
} }
@Test fun calculateValidGlucoseStatus() { @Test fun calculateValidGlucoseStatus() {
Mockito.`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateValidBgData()) Mockito.`when`(autosensDataStore.getBucketedDataTableCopy()).thenReturn(generateValidBgData())
val glucoseStatus = GlucoseStatusProviderImpl(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData!! val glucoseStatus = GlucoseStatusProviderImpl(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData!!
Assertions.assertEquals(214.0, glucoseStatus.glucose, 0.001) Assertions.assertEquals(214.0, glucoseStatus.glucose, 0.001)
Assertions.assertEquals(-2.0, glucoseStatus.delta, 0.001) Assertions.assertEquals(-2.0, glucoseStatus.delta, 0.001)
@ -48,9 +49,11 @@ class GlucoseStatusTest : TestBase() {
Assertions.assertEquals(-2.0, glucoseStatus.longAvgDelta, 0.001) // -2 -2 -2 -2 Assertions.assertEquals(-2.0, glucoseStatus.longAvgDelta, 0.001) // -2 -2 -2 -2
Assertions.assertEquals(1514766900000L, glucoseStatus.date) // latest date Assertions.assertEquals(1514766900000L, glucoseStatus.date) // latest date
} }
/*
Not testing anymore, not valid for bucketed data
@Test fun calculateMostRecentGlucoseStatus() { @Test fun calculateMostRecentGlucoseStatus() {
Mockito.`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateMostRecentBgData()) Mockito.`when`(autosensDataStore.getBucketedDataTableCopy()).thenReturn(generateMostRecentBgData())
val glucoseStatus: GlucoseStatus = GlucoseStatusProviderImpl(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData!! val glucoseStatus: GlucoseStatus = GlucoseStatusProviderImpl(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData!!
Assertions.assertEquals(215.0, glucoseStatus.glucose, 0.001) // (214+216) / 2 Assertions.assertEquals(215.0, glucoseStatus.glucose, 0.001) // (214+216) / 2
Assertions.assertEquals(-1.0, glucoseStatus.delta, 0.001) Assertions.assertEquals(-1.0, glucoseStatus.delta, 0.001)
@ -59,8 +62,17 @@ class GlucoseStatusTest : TestBase() {
Assertions.assertEquals(1514766900000L, glucoseStatus.date) // latest date, even when averaging Assertions.assertEquals(1514766900000L, glucoseStatus.date) // latest date, even when averaging
} }
private fun generateMostRecentBgData(): MutableList<InMemoryGlucoseValue> {
val list: MutableList<InMemoryGlucoseValue> = ArrayList()
list.add(InMemoryGlucoseValue(value = 214.0, timestamp = 1514766900000, trendArrow = GlucoseValue.TrendArrow.FLAT))
list.add(InMemoryGlucoseValue(value = 216.0, timestamp = 1514766800000, trendArrow = GlucoseValue.TrendArrow.FLAT))
list.add(InMemoryGlucoseValue(value = 216.0, timestamp = 1514766600000, trendArrow = GlucoseValue.TrendArrow.FLAT))
return list
}
*/
@Test fun oneRecordShouldProduceZeroDeltas() { @Test fun oneRecordShouldProduceZeroDeltas() {
Mockito.`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateOneCurrentRecordBgData()) Mockito.`when`(autosensDataStore.getBucketedDataTableCopy()).thenReturn(generateOneCurrentRecordBgData())
val glucoseStatus: GlucoseStatus = GlucoseStatusProviderImpl(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData!! val glucoseStatus: GlucoseStatus = GlucoseStatusProviderImpl(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData!!
Assertions.assertEquals(214.0, glucoseStatus.glucose, 0.001) Assertions.assertEquals(214.0, glucoseStatus.glucose, 0.001)
Assertions.assertEquals(0.0, glucoseStatus.delta, 0.001) Assertions.assertEquals(0.0, glucoseStatus.delta, 0.001)
@ -70,19 +82,19 @@ class GlucoseStatusTest : TestBase() {
} }
@Test fun insufficientDataShouldReturnNull() { @Test fun insufficientDataShouldReturnNull() {
Mockito.`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateInsufficientBgData()) Mockito.`when`(autosensDataStore.getBucketedDataTableCopy()).thenReturn(generateInsufficientBgData())
val glucoseStatus: GlucoseStatus? = GlucoseStatusProviderImpl(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData val glucoseStatus: GlucoseStatus? = GlucoseStatusProviderImpl(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData
Assertions.assertEquals(null, glucoseStatus) Assertions.assertEquals(null, glucoseStatus)
} }
@Test fun oldDataShouldReturnNull() { @Test fun oldDataShouldReturnNull() {
Mockito.`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateOldBgData()) Mockito.`when`(autosensDataStore.getBucketedDataTableCopy()).thenReturn(generateOldBgData())
val glucoseStatus: GlucoseStatus? = GlucoseStatusProviderImpl(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData val glucoseStatus: GlucoseStatus? = GlucoseStatusProviderImpl(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData
Assertions.assertEquals(null, glucoseStatus) Assertions.assertEquals(null, glucoseStatus)
} }
@Test fun returnOldDataIfAllowed() { @Test fun returnOldDataIfAllowed() {
Mockito.`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateOldBgData()) Mockito.`when`(autosensDataStore.getBucketedDataTableCopy()).thenReturn(generateOldBgData())
val glucoseStatus: GlucoseStatus? = GlucoseStatusProviderImpl(aapsLogger, iobCobCalculatorPlugin, dateUtil).getGlucoseStatusData(true) val glucoseStatus: GlucoseStatus? = GlucoseStatusProviderImpl(aapsLogger, iobCobCalculatorPlugin, dateUtil).getGlucoseStatusData(true)
Assertions.assertNotEquals(null, glucoseStatus) Assertions.assertNotEquals(null, glucoseStatus)
} }
@ -91,8 +103,11 @@ class GlucoseStatusTest : TestBase() {
Assertions.assertEquals(0.0, GlucoseStatusProviderImpl.average(ArrayList()), 0.001) Assertions.assertEquals(0.0, GlucoseStatusProviderImpl.average(ArrayList()), 0.001)
} }
/*
Not testing anymore, not valid for bucketed data
@Test fun calculateGlucoseStatusForLibreTestBgData() { @Test fun calculateGlucoseStatusForLibreTestBgData() {
Mockito.`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateLibreTestData()) Mockito.`when`(autosensDataStore.getBucketedDataTableCopy()).thenReturn(generateLibreTestData())
val glucoseStatus: GlucoseStatus = GlucoseStatusProviderImpl(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData!! val glucoseStatus: GlucoseStatus = GlucoseStatusProviderImpl(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData!!
Assertions.assertEquals(100.0, glucoseStatus.glucose, 0.001) // Assertions.assertEquals(100.0, glucoseStatus.glucose, 0.001) //
Assertions.assertEquals(-10.0, glucoseStatus.delta, 0.001) Assertions.assertEquals(-10.0, glucoseStatus.delta, 0.001)
@ -101,6 +116,23 @@ class GlucoseStatusTest : TestBase() {
Assertions.assertEquals(1514766900000L, glucoseStatus.date) // latest date Assertions.assertEquals(1514766900000L, glucoseStatus.date) // latest date
} }
private fun generateLibreTestData(): MutableList<InMemoryGlucoseValue> {
val list: MutableList<InMemoryGlucoseValue> = ArrayList()
val endTime = 1514766900000L
val latestReading = 100.0
// Now
list.add(InMemoryGlucoseValue(value = latestReading, timestamp = endTime, trendArrow = GlucoseValue.TrendArrow.FLAT))
// One minute ago
list.add(InMemoryGlucoseValue(value = latestReading, timestamp = endTime - 1000 * 60 * 1, trendArrow = GlucoseValue.TrendArrow.FLAT))
// Two minutes ago
list.add(InMemoryGlucoseValue(value = latestReading, timestamp = endTime - 1000 * 60 * 2, trendArrow = GlucoseValue.TrendArrow.FLAT))
// Three minutes and beyond at constant rate
for (i in 3..49)
list.add(InMemoryGlucoseValue(value = latestReading + i * 2, timestamp = endTime - 1000 * 60 * i, trendArrow = GlucoseValue.TrendArrow.FLAT))
return list
}
*/
@BeforeEach @BeforeEach
fun initMocking() { fun initMocking() {
Mockito.`when`(dateUtil.now()).thenReturn(1514766900000L + T.mins(1).msecs()) Mockito.`when`(dateUtil.now()).thenReturn(1514766900000L + T.mins(1).msecs())
@ -108,84 +140,32 @@ class GlucoseStatusTest : TestBase() {
} }
// [{"mgdl":214,"mills":1521895773113,"device":"xDrip-DexcomG5","direction":"Flat","filtered":191040,"unfiltered":205024,"noise":1,"rssi":100},{"mgdl":219,"mills":1521896073352,"device":"xDrip-DexcomG5","direction":"Flat","filtered":200160,"unfiltered":209760,"noise":1,"rssi":100},{"mgdl":222,"mills":1521896372890,"device":"xDrip-DexcomG5","direction":"Flat","filtered":207360,"unfiltered":212512,"noise":1,"rssi":100},{"mgdl":220,"mills":1521896673062,"device":"xDrip-DexcomG5","direction":"Flat","filtered":211488,"unfiltered":210688,"noise":1,"rssi":100},{"mgdl":193,"mills":1521896972933,"device":"xDrip-DexcomG5","direction":"Flat","filtered":212384,"unfiltered":208960,"noise":1,"rssi":100},{"mgdl":181,"mills":1521897273336,"device":"xDrip-DexcomG5","direction":"SingleDown","filtered":210592,"unfiltered":204320,"noise":1,"rssi":100},{"mgdl":176,"mills":1521897572875,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":206720,"unfiltered":197440,"noise":1,"rssi":100},{"mgdl":168,"mills":1521897872929,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":201024,"unfiltered":187904,"noise":1,"rssi":100},{"mgdl":161,"mills":1521898172814,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":193376,"unfiltered":178144,"noise":1,"rssi":100},{"mgdl":148,"mills":1521898472879,"device":"xDrip-DexcomG5","direction":"SingleDown","filtered":183264,"unfiltered":161216,"noise":1,"rssi":100},{"mgdl":139,"mills":1521898772862,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":170784,"unfiltered":148928,"noise":1,"rssi":100},{"mgdl":132,"mills":1521899072896,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":157248,"unfiltered":139552,"noise":1,"rssi":100},{"mgdl":125,"mills":1521899372834,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":144416,"unfiltered":129616.00000000001,"noise":1,"rssi":100},{"mgdl":128,"mills":1521899973456,"device":"xDrip-DexcomG5","direction":"Flat","filtered":130240.00000000001,"unfiltered":133536,"noise":1,"rssi":100},{"mgdl":132,"mills":1521900573287,"device":"xDrip-DexcomG5","direction":"Flat","filtered":133504,"unfiltered":138720,"noise":1,"rssi":100},{"mgdl":127,"mills":1521900873711,"device":"xDrip-DexcomG5","direction":"Flat","filtered":136480,"unfiltered":132992,"noise":1,"rssi":100},{"mgdl":127,"mills":1521901180151,"device":"xDrip-DexcomG5","direction":"Flat","filtered":136896,"unfiltered":132128,"noise":1,"rssi":100},{"mgdl":125,"mills":1521901473582,"device":"xDrip-DexcomG5","direction":"Flat","filtered":134624,"unfiltered":129696,"noise":1,"rssi":100},{"mgdl":120,"mills":1521901773597,"device":"xDrip-DexcomG5","direction":"Flat","filtered":130704.00000000001,"unfiltered":123376,"noise":1,"rssi":100},{"mgdl":116,"mills":1521902075855,"device":"xDrip-DexcomG5","direction":"Flat","filtered":126272,"unfiltered":118448,"noise":1,"rssi":100}] // [{"mgdl":214,"mills":1521895773113,"device":"xDrip-DexcomG5","direction":"Flat","filtered":191040,"unfiltered":205024,"noise":1,"rssi":100},{"mgdl":219,"mills":1521896073352,"device":"xDrip-DexcomG5","direction":"Flat","filtered":200160,"unfiltered":209760,"noise":1,"rssi":100},{"mgdl":222,"mills":1521896372890,"device":"xDrip-DexcomG5","direction":"Flat","filtered":207360,"unfiltered":212512,"noise":1,"rssi":100},{"mgdl":220,"mills":1521896673062,"device":"xDrip-DexcomG5","direction":"Flat","filtered":211488,"unfiltered":210688,"noise":1,"rssi":100},{"mgdl":193,"mills":1521896972933,"device":"xDrip-DexcomG5","direction":"Flat","filtered":212384,"unfiltered":208960,"noise":1,"rssi":100},{"mgdl":181,"mills":1521897273336,"device":"xDrip-DexcomG5","direction":"SingleDown","filtered":210592,"unfiltered":204320,"noise":1,"rssi":100},{"mgdl":176,"mills":1521897572875,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":206720,"unfiltered":197440,"noise":1,"rssi":100},{"mgdl":168,"mills":1521897872929,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":201024,"unfiltered":187904,"noise":1,"rssi":100},{"mgdl":161,"mills":1521898172814,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":193376,"unfiltered":178144,"noise":1,"rssi":100},{"mgdl":148,"mills":1521898472879,"device":"xDrip-DexcomG5","direction":"SingleDown","filtered":183264,"unfiltered":161216,"noise":1,"rssi":100},{"mgdl":139,"mills":1521898772862,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":170784,"unfiltered":148928,"noise":1,"rssi":100},{"mgdl":132,"mills":1521899072896,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":157248,"unfiltered":139552,"noise":1,"rssi":100},{"mgdl":125,"mills":1521899372834,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":144416,"unfiltered":129616.00000000001,"noise":1,"rssi":100},{"mgdl":128,"mills":1521899973456,"device":"xDrip-DexcomG5","direction":"Flat","filtered":130240.00000000001,"unfiltered":133536,"noise":1,"rssi":100},{"mgdl":132,"mills":1521900573287,"device":"xDrip-DexcomG5","direction":"Flat","filtered":133504,"unfiltered":138720,"noise":1,"rssi":100},{"mgdl":127,"mills":1521900873711,"device":"xDrip-DexcomG5","direction":"Flat","filtered":136480,"unfiltered":132992,"noise":1,"rssi":100},{"mgdl":127,"mills":1521901180151,"device":"xDrip-DexcomG5","direction":"Flat","filtered":136896,"unfiltered":132128,"noise":1,"rssi":100},{"mgdl":125,"mills":1521901473582,"device":"xDrip-DexcomG5","direction":"Flat","filtered":134624,"unfiltered":129696,"noise":1,"rssi":100},{"mgdl":120,"mills":1521901773597,"device":"xDrip-DexcomG5","direction":"Flat","filtered":130704.00000000001,"unfiltered":123376,"noise":1,"rssi":100},{"mgdl":116,"mills":1521902075855,"device":"xDrip-DexcomG5","direction":"Flat","filtered":126272,"unfiltered":118448,"noise":1,"rssi":100}]
private fun generateValidBgData(): List<GlucoseValue> { private fun generateValidBgData(): MutableList<InMemoryGlucoseValue> {
val list: MutableList<GlucoseValue> = ArrayList() val list: MutableList<InMemoryGlucoseValue> = ArrayList()
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 214.0, timestamp = 1514766900000, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) list.add(InMemoryGlucoseValue(value = 214.0, timestamp = 1514766900000, trendArrow = GlucoseValue.TrendArrow.FLAT))
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 216.0, timestamp = 1514766600000, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) list.add(InMemoryGlucoseValue(value = 216.0, timestamp = 1514766600000, trendArrow = GlucoseValue.TrendArrow.FLAT))
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 219.0, timestamp = 1514766300000, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) list.add(InMemoryGlucoseValue(value = 219.0, timestamp = 1514766300000, trendArrow = GlucoseValue.TrendArrow.FLAT))
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 223.0, timestamp = 1514766000000, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) list.add(InMemoryGlucoseValue(value = 223.0, timestamp = 1514766000000, trendArrow = GlucoseValue.TrendArrow.FLAT))
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 222.0, timestamp = 1514765700000, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) list.add(InMemoryGlucoseValue(value = 222.0, timestamp = 1514765700000, trendArrow = GlucoseValue.TrendArrow.FLAT))
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 224.0, timestamp = 1514765400000, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) list.add(InMemoryGlucoseValue(value = 224.0, timestamp = 1514765400000, trendArrow = GlucoseValue.TrendArrow.FLAT))
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 226.0, timestamp = 1514765100000, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) list.add(InMemoryGlucoseValue(value = 226.0, timestamp = 1514765100000, trendArrow = GlucoseValue.TrendArrow.FLAT))
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 228.0, timestamp = 1514764800000, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) list.add(InMemoryGlucoseValue(value = 228.0, timestamp = 1514764800000, trendArrow = GlucoseValue.TrendArrow.FLAT))
return list return list
} }
private fun generateMostRecentBgData(): List<GlucoseValue> { private fun generateInsufficientBgData(): MutableList<InMemoryGlucoseValue> {
val list: MutableList<GlucoseValue> = ArrayList()
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 214.0, timestamp = 1514766900000, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 216.0, timestamp = 1514766800000, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 216.0, timestamp = 1514766600000, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
return list
}
private fun generateInsufficientBgData(): List<GlucoseValue> {
return ArrayList() return ArrayList()
} }
private fun generateOldBgData(): List<GlucoseValue> { private fun generateOldBgData(): MutableList<InMemoryGlucoseValue> {
val list: MutableList<GlucoseValue> = ArrayList() val list: MutableList<InMemoryGlucoseValue> = ArrayList()
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 228.0, timestamp = 1514764800000, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) list.add(InMemoryGlucoseValue(value = 228.0, timestamp = 1514764800000, trendArrow = GlucoseValue.TrendArrow.FLAT))
return list return list
} }
private fun generateOneCurrentRecordBgData(): List<GlucoseValue> { private fun generateOneCurrentRecordBgData(): MutableList<InMemoryGlucoseValue> {
val list: MutableList<GlucoseValue> = ArrayList() val list: MutableList<InMemoryGlucoseValue> = ArrayList()
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 214.0, timestamp = 1514766900000, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) list.add(InMemoryGlucoseValue(value = 214.0, timestamp = 1514766900000, trendArrow = GlucoseValue.TrendArrow.FLAT))
return list
}
private fun generateLibreTestData(): List<GlucoseValue> {
val list: MutableList<GlucoseValue> = ArrayList()
val endTime = 1514766900000L
val latestReading = 100.0
// Now
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = latestReading, timestamp = endTime, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
// One minute ago
list.add(
GlucoseValue(
raw = 0.0,
noise = 0.0,
value = latestReading,
timestamp = endTime - 1000 * 60 * 1,
sourceSensor = GlucoseValue.SourceSensor.UNKNOWN,
trendArrow = GlucoseValue.TrendArrow.FLAT
)
)
// Two minutes ago
list.add(
GlucoseValue(
raw = 0.0,
noise = 0.0,
value = latestReading,
timestamp = endTime - 1000 * 60 * 2,
sourceSensor = GlucoseValue.SourceSensor.UNKNOWN,
trendArrow = GlucoseValue.TrendArrow.FLAT
)
)
// Three minutes and beyond at constant rate
for (i in 3..49)
list.add(
GlucoseValue(
raw = 0.0,
noise = 0.0,
value = latestReading + i * 2,
timestamp = endTime - 1000 * 60 * i,
sourceSensor = GlucoseValue.SourceSensor.UNKNOWN,
trendArrow = GlucoseValue.TrendArrow.FLAT
)
)
return list return list
} }
} }

View file

@ -4,8 +4,9 @@ import com.google.common.base.Optional
import info.nightscout.automation.elements.Comparator import info.nightscout.automation.elements.Comparator
import info.nightscout.database.entities.GlucoseValue import info.nightscout.database.entities.GlucoseValue
import info.nightscout.interfaces.GlucoseUnit import info.nightscout.interfaces.GlucoseUnit
import info.nightscout.interfaces.iob.InMemoryGlucoseValue
import org.json.JSONObject import org.json.JSONObject
import org.junit.Assert import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.mockito.Mockito.`when` import org.mockito.Mockito.`when`
@ -22,39 +23,39 @@ class TriggerBgTest : TriggerTestBase() {
@Test @Test
fun shouldRunTest() { fun shouldRunTest() {
`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateOneCurrentRecordBgData()) `when`(autosensDataStore.getBucketedDataTableCopy()).thenReturn(generateOneCurrentRecordBgData())
var t: TriggerBg = TriggerBg(injector).setUnits(GlucoseUnit.MMOL).setValue(4.1).comparator(Comparator.Compare.IS_EQUAL) var t: TriggerBg = TriggerBg(injector).setUnits(GlucoseUnit.MMOL).setValue(4.1).comparator(Comparator.Compare.IS_EQUAL)
Assert.assertFalse(t.shouldRun()) Assertions.assertFalse(t.shouldRun())
t = TriggerBg(injector).setUnits(GlucoseUnit.MGDL).setValue(214.0).comparator(Comparator.Compare.IS_EQUAL) t = TriggerBg(injector).setUnits(GlucoseUnit.MGDL).setValue(214.0).comparator(Comparator.Compare.IS_EQUAL)
Assert.assertTrue(t.shouldRun()) Assertions.assertTrue(t.shouldRun())
t = TriggerBg(injector).setUnits(GlucoseUnit.MGDL).setValue(214.0).comparator(Comparator.Compare.IS_EQUAL_OR_GREATER) t = TriggerBg(injector).setUnits(GlucoseUnit.MGDL).setValue(214.0).comparator(Comparator.Compare.IS_EQUAL_OR_GREATER)
Assert.assertTrue(t.shouldRun()) Assertions.assertTrue(t.shouldRun())
t = TriggerBg(injector).setUnits(GlucoseUnit.MGDL).setValue(214.0).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER) t = TriggerBg(injector).setUnits(GlucoseUnit.MGDL).setValue(214.0).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER)
Assert.assertTrue(t.shouldRun()) Assertions.assertTrue(t.shouldRun())
t = TriggerBg(injector).setUnits(GlucoseUnit.MGDL).setValue(215.0).comparator(Comparator.Compare.IS_EQUAL) t = TriggerBg(injector).setUnits(GlucoseUnit.MGDL).setValue(215.0).comparator(Comparator.Compare.IS_EQUAL)
Assert.assertFalse(t.shouldRun()) Assertions.assertFalse(t.shouldRun())
t = TriggerBg(injector).setUnits(GlucoseUnit.MGDL).setValue(215.0).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER) t = TriggerBg(injector).setUnits(GlucoseUnit.MGDL).setValue(215.0).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER)
Assert.assertTrue(t.shouldRun()) Assertions.assertTrue(t.shouldRun())
t = TriggerBg(injector).setUnits(GlucoseUnit.MGDL).setValue(215.0).comparator(Comparator.Compare.IS_EQUAL_OR_GREATER) t = TriggerBg(injector).setUnits(GlucoseUnit.MGDL).setValue(215.0).comparator(Comparator.Compare.IS_EQUAL_OR_GREATER)
Assert.assertFalse(t.shouldRun()) Assertions.assertFalse(t.shouldRun())
t = TriggerBg(injector).setUnits(GlucoseUnit.MGDL).setValue(213.0).comparator(Comparator.Compare.IS_EQUAL_OR_GREATER) t = TriggerBg(injector).setUnits(GlucoseUnit.MGDL).setValue(213.0).comparator(Comparator.Compare.IS_EQUAL_OR_GREATER)
Assert.assertTrue(t.shouldRun()) Assertions.assertTrue(t.shouldRun())
t = TriggerBg(injector).setUnits(GlucoseUnit.MGDL).setValue(213.0).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER) t = TriggerBg(injector).setUnits(GlucoseUnit.MGDL).setValue(213.0).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER)
Assert.assertFalse(t.shouldRun()) Assertions.assertFalse(t.shouldRun())
`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(ArrayList()) `when`(autosensDataStore.getBucketedDataTableCopy()).thenReturn(ArrayList())
t = TriggerBg(injector).setUnits(GlucoseUnit.MGDL).setValue(213.0).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER) t = TriggerBg(injector).setUnits(GlucoseUnit.MGDL).setValue(213.0).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER)
Assert.assertFalse(t.shouldRun()) Assertions.assertFalse(t.shouldRun())
t = TriggerBg(injector).comparator(Comparator.Compare.IS_NOT_AVAILABLE) t = TriggerBg(injector).comparator(Comparator.Compare.IS_NOT_AVAILABLE)
Assert.assertTrue(t.shouldRun()) Assertions.assertTrue(t.shouldRun())
} }
@Test @Test
fun copyConstructorTest() { fun copyConstructorTest() {
val t: TriggerBg = TriggerBg(injector).setUnits(GlucoseUnit.MGDL).setValue(213.0).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER) val t: TriggerBg = TriggerBg(injector).setUnits(GlucoseUnit.MGDL).setValue(213.0).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER)
val t1 = t.duplicate() as TriggerBg val t1 = t.duplicate() as TriggerBg
Assert.assertEquals(213.0, t1.bg.value, 0.01) Assertions.assertEquals(213.0, t1.bg.value, 0.01)
Assert.assertEquals(GlucoseUnit.MGDL, t1.bg.units) Assertions.assertEquals(GlucoseUnit.MGDL, t1.bg.units)
Assert.assertEquals(Comparator.Compare.IS_EQUAL_OR_LESSER, t.comparator.value) Assertions.assertEquals(Comparator.Compare.IS_EQUAL_OR_LESSER, t.comparator.value)
} }
private var bgJson = "{\"data\":{\"comparator\":\"IS_EQUAL\",\"bg\":4.1,\"units\":\"mmol\"},\"type\":\"TriggerBg\"}" private var bgJson = "{\"data\":{\"comparator\":\"IS_EQUAL\",\"bg\":4.1,\"units\":\"mmol\"},\"type\":\"TriggerBg\"}"
@ -62,35 +63,26 @@ class TriggerBgTest : TriggerTestBase() {
@Test @Test
fun toJSONTest() { fun toJSONTest() {
val t: TriggerBg = TriggerBg(injector).setUnits(GlucoseUnit.MMOL).setValue(4.1).comparator(Comparator.Compare.IS_EQUAL) val t: TriggerBg = TriggerBg(injector).setUnits(GlucoseUnit.MMOL).setValue(4.1).comparator(Comparator.Compare.IS_EQUAL)
Assert.assertEquals(bgJson, t.toJSON()) Assertions.assertEquals(bgJson, t.toJSON())
} }
@Test @Test
fun fromJSONTest() { fun fromJSONTest() {
val t: TriggerBg = TriggerBg(injector).setUnits(GlucoseUnit.MMOL).setValue(4.1).comparator(Comparator.Compare.IS_EQUAL) val t: TriggerBg = TriggerBg(injector).setUnits(GlucoseUnit.MMOL).setValue(4.1).comparator(Comparator.Compare.IS_EQUAL)
val t2 = TriggerDummy(injector).instantiate(JSONObject(t.toJSON())) as TriggerBg val t2 = TriggerDummy(injector).instantiate(JSONObject(t.toJSON())) as TriggerBg
Assert.assertEquals(Comparator.Compare.IS_EQUAL, t2.comparator.value) Assertions.assertEquals(Comparator.Compare.IS_EQUAL, t2.comparator.value)
Assert.assertEquals(4.1, t2.bg.value, 0.01) Assertions.assertEquals(4.1, t2.bg.value, 0.01)
Assert.assertEquals(GlucoseUnit.MMOL, t2.bg.units) Assertions.assertEquals(GlucoseUnit.MMOL, t2.bg.units)
} }
@Test @Test
fun iconTest() { fun iconTest() {
Assert.assertEquals(Optional.of(info.nightscout.core.main.R.drawable.ic_cp_bgcheck), TriggerBg(injector).icon()) Assertions.assertEquals(Optional.of(info.nightscout.core.main.R.drawable.ic_cp_bgcheck), TriggerBg(injector).icon())
} }
private fun generateOneCurrentRecordBgData(): List<GlucoseValue> { private fun generateOneCurrentRecordBgData(): MutableList<InMemoryGlucoseValue> {
val list: MutableList<GlucoseValue> = ArrayList() val list: MutableList<InMemoryGlucoseValue> = ArrayList()
list.add( list.add(InMemoryGlucoseValue(value = 214.0, timestamp = now - 1, trendArrow = GlucoseValue.TrendArrow.FLAT))
GlucoseValue(
raw = 0.0,
noise = 0.0,
value = 214.0,
timestamp = now - 1,
sourceSensor = GlucoseValue.SourceSensor.UNKNOWN,
trendArrow = GlucoseValue.TrendArrow.FLAT
)
)
return list return list
} }
} }

View file

@ -6,8 +6,9 @@ import info.nightscout.automation.elements.Comparator
import info.nightscout.automation.elements.InputDelta.DeltaType import info.nightscout.automation.elements.InputDelta.DeltaType
import info.nightscout.database.entities.GlucoseValue import info.nightscout.database.entities.GlucoseValue
import info.nightscout.interfaces.GlucoseUnit import info.nightscout.interfaces.GlucoseUnit
import info.nightscout.interfaces.iob.InMemoryGlucoseValue
import org.json.JSONObject import org.json.JSONObject
import org.junit.Assert import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.mockito.Mockito.`when` import org.mockito.Mockito.`when`
@ -23,42 +24,42 @@ class TriggerDeltaTest : TriggerTestBase() {
} }
@Test fun shouldRunTest() { @Test fun shouldRunTest() {
`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateValidBgData()) `when`(autosensDataStore.getBucketedDataTableCopy()).thenReturn(generateValidBgData())
var t = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(73.0, DeltaType.LONG_AVERAGE).comparator(Comparator.Compare.IS_EQUAL) var t = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(73.0, DeltaType.LONG_AVERAGE).comparator(Comparator.Compare.IS_EQUAL)
Assert.assertFalse(t.shouldRun()) Assertions.assertFalse(t.shouldRun())
Assert.assertEquals(DeltaType.LONG_AVERAGE, t.delta.deltaType) Assertions.assertEquals(DeltaType.LONG_AVERAGE, t.delta.deltaType)
t = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(-2.0, DeltaType.SHORT_AVERAGE).comparator(Comparator.Compare.IS_EQUAL) t = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(-2.0, DeltaType.SHORT_AVERAGE).comparator(Comparator.Compare.IS_EQUAL)
Assert.assertFalse(t.shouldRun()) Assertions.assertFalse(t.shouldRun())
Assert.assertEquals(DeltaType.SHORT_AVERAGE, t.delta.deltaType) Assertions.assertEquals(DeltaType.SHORT_AVERAGE, t.delta.deltaType)
t = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(-3.0, DeltaType.DELTA).comparator(Comparator.Compare.IS_EQUAL_OR_GREATER) t = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(-3.0, DeltaType.DELTA).comparator(Comparator.Compare.IS_EQUAL_OR_GREATER)
Assert.assertTrue(t.shouldRun()) Assertions.assertTrue(t.shouldRun())
Assert.assertEquals(DeltaType.DELTA, t.delta.deltaType) Assertions.assertEquals(DeltaType.DELTA, t.delta.deltaType)
t = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(2.0, DeltaType.LONG_AVERAGE).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER) t = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(2.0, DeltaType.LONG_AVERAGE).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER)
Assert.assertTrue(t.shouldRun()) Assertions.assertTrue(t.shouldRun())
t = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(2.0, DeltaType.LONG_AVERAGE).comparator(Comparator.Compare.IS_EQUAL) t = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(2.0, DeltaType.LONG_AVERAGE).comparator(Comparator.Compare.IS_EQUAL)
Assert.assertFalse(t.shouldRun()) Assertions.assertFalse(t.shouldRun())
t = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(0.3, DeltaType.LONG_AVERAGE).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER) t = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(0.3, DeltaType.LONG_AVERAGE).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER)
Assert.assertTrue(t.shouldRun()) Assertions.assertTrue(t.shouldRun())
t = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(0.1, DeltaType.LONG_AVERAGE).comparator(Comparator.Compare.IS_EQUAL_OR_GREATER) t = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(0.1, DeltaType.LONG_AVERAGE).comparator(Comparator.Compare.IS_EQUAL_OR_GREATER)
Assert.assertFalse(t.shouldRun()) Assertions.assertFalse(t.shouldRun())
t = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(-0.5, DeltaType.LONG_AVERAGE).comparator(Comparator.Compare.IS_EQUAL_OR_GREATER) t = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(-0.5, DeltaType.LONG_AVERAGE).comparator(Comparator.Compare.IS_EQUAL_OR_GREATER)
Assert.assertFalse(t.shouldRun()) Assertions.assertFalse(t.shouldRun())
t = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(-0.2, DeltaType.LONG_AVERAGE).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER) t = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(-0.2, DeltaType.LONG_AVERAGE).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER)
Assert.assertTrue(t.shouldRun()) Assertions.assertTrue(t.shouldRun())
`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(ArrayList()) `when`(autosensDataStore.getBucketedDataTableCopy()).thenReturn(ArrayList())
t = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(213.0, DeltaType.DELTA).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER) t = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(213.0, DeltaType.DELTA).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER)
Assert.assertFalse(t.shouldRun()) Assertions.assertFalse(t.shouldRun())
t = TriggerDelta(injector).comparator(Comparator.Compare.IS_NOT_AVAILABLE) t = TriggerDelta(injector).comparator(Comparator.Compare.IS_NOT_AVAILABLE)
Assert.assertTrue(t.shouldRun()) Assertions.assertTrue(t.shouldRun())
} }
@Test fun copyConstructorTest() { @Test fun copyConstructorTest() {
val t: TriggerDelta = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(213.0, DeltaType.DELTA).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER) val t: TriggerDelta = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(213.0, DeltaType.DELTA).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER)
val t1 = t.duplicate() as TriggerDelta val t1 = t.duplicate() as TriggerDelta
Assert.assertEquals(213.0, t1.delta.value, 0.01) Assertions.assertEquals(213.0, t1.delta.value, 0.01)
Assert.assertEquals(GlucoseUnit.MGDL, t1.units) Assertions.assertEquals(GlucoseUnit.MGDL, t1.units)
Assert.assertEquals(DeltaType.DELTA, t.delta.deltaType) Assertions.assertEquals(DeltaType.DELTA, t.delta.deltaType)
Assert.assertEquals(Comparator.Compare.IS_EQUAL_OR_LESSER, t.comparator.value) Assertions.assertEquals(Comparator.Compare.IS_EQUAL_OR_LESSER, t.comparator.value)
} }
private var deltaJson = "{\"data\":{\"comparator\":\"IS_EQUAL\",\"deltaType\":\"DELTA\",\"units\":\"mg/dl\",\"value\":4.1},\"type\":\"TriggerDelta\"}" private var deltaJson = "{\"data\":{\"comparator\":\"IS_EQUAL\",\"deltaType\":\"DELTA\",\"units\":\"mg/dl\",\"value\":4.1},\"type\":\"TriggerDelta\"}"
@ -66,38 +67,38 @@ class TriggerDeltaTest : TriggerTestBase() {
@Test @Test
fun toJSONTest() { fun toJSONTest() {
val t: TriggerDelta = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(4.1, DeltaType.DELTA).comparator(Comparator.Compare.IS_EQUAL) val t: TriggerDelta = TriggerDelta(injector).units(GlucoseUnit.MGDL).setValue(4.1, DeltaType.DELTA).comparator(Comparator.Compare.IS_EQUAL)
Assert.assertEquals(deltaJson, t.toJSON()) Assertions.assertEquals(deltaJson, t.toJSON())
} }
@Test @Test
fun fromJSONTest() { fun fromJSONTest() {
val t: TriggerDelta = TriggerDelta(injector).units(GlucoseUnit.MMOL).setValue(4.1, DeltaType.DELTA).comparator(Comparator.Compare.IS_EQUAL) val t: TriggerDelta = TriggerDelta(injector).units(GlucoseUnit.MMOL).setValue(4.1, DeltaType.DELTA).comparator(Comparator.Compare.IS_EQUAL)
val t2 = TriggerDummy(injector).instantiate(JSONObject(t.toJSON())) as TriggerDelta val t2 = TriggerDummy(injector).instantiate(JSONObject(t.toJSON())) as TriggerDelta
Assert.assertEquals(Comparator.Compare.IS_EQUAL, t2.comparator.value) Assertions.assertEquals(Comparator.Compare.IS_EQUAL, t2.comparator.value)
Assert.assertEquals(4.1, t2.delta.value, 0.01) Assertions.assertEquals(4.1, t2.delta.value, 0.01)
Assert.assertEquals(GlucoseUnit.MMOL, t2.units) Assertions.assertEquals(GlucoseUnit.MMOL, t2.units)
Assert.assertEquals(DeltaType.DELTA, t2.delta.deltaType) Assertions.assertEquals(DeltaType.DELTA, t2.delta.deltaType)
} }
@Test fun iconTest() { @Test fun iconTest() {
Assert.assertEquals(Optional.of(R.drawable.ic_auto_delta), TriggerDelta(injector).icon()) Assertions.assertEquals(Optional.of(R.drawable.ic_auto_delta), TriggerDelta(injector).icon())
} }
@Test fun initializerTest() { @Test fun initializerTest() {
val t = TriggerDelta(injector) val t = TriggerDelta(injector)
Assert.assertTrue(t.units == GlucoseUnit.MGDL) Assertions.assertTrue(t.units == GlucoseUnit.MGDL)
} }
private fun generateValidBgData(): List<GlucoseValue> { private fun generateValidBgData(): MutableList<InMemoryGlucoseValue> {
val list: MutableList<GlucoseValue> = ArrayList() val list: MutableList<InMemoryGlucoseValue> = ArrayList()
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 214.0, timestamp = 1514766900000, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) list.add(InMemoryGlucoseValue(value = 214.0, timestamp = 1514766900000, trendArrow = GlucoseValue.TrendArrow.FLAT))
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 216.0, timestamp = 1514766600000, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) list.add(InMemoryGlucoseValue(value = 216.0, timestamp = 1514766600000, trendArrow = GlucoseValue.TrendArrow.FLAT))
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 219.0, timestamp = 1514766300000, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) list.add(InMemoryGlucoseValue(value = 219.0, timestamp = 1514766300000, trendArrow = GlucoseValue.TrendArrow.FLAT))
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 223.0, timestamp = 1514766000000, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) list.add(InMemoryGlucoseValue(value = 223.0, timestamp = 1514766000000, trendArrow = GlucoseValue.TrendArrow.FLAT))
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 222.0, timestamp = 1514765700000, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) list.add(InMemoryGlucoseValue(value = 222.0, timestamp = 1514765700000, trendArrow = GlucoseValue.TrendArrow.FLAT))
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 224.0, timestamp = 1514765400000, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) list.add(InMemoryGlucoseValue(value = 224.0, timestamp = 1514765400000, trendArrow = GlucoseValue.TrendArrow.FLAT))
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 226.0, timestamp = 1514765100000, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) list.add(InMemoryGlucoseValue(value = 226.0, timestamp = 1514765100000, trendArrow = GlucoseValue.TrendArrow.FLAT))
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 228.0, timestamp = 1514764800000, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) list.add(InMemoryGlucoseValue(value = 228.0, timestamp = 1514764800000, trendArrow = GlucoseValue.TrendArrow.FLAT))
return list return list
} }
} }

View file

@ -80,10 +80,10 @@ import info.nightscout.plugins.ui.StatusLightHandler
import info.nightscout.rx.AapsSchedulers import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventAcceptOpenLoopChange import info.nightscout.rx.events.EventAcceptOpenLoopChange
import info.nightscout.rx.events.EventBucketedDataCreated
import info.nightscout.rx.events.EventEffectiveProfileSwitchChanged import info.nightscout.rx.events.EventEffectiveProfileSwitchChanged
import info.nightscout.rx.events.EventExtendedBolusChange import info.nightscout.rx.events.EventExtendedBolusChange
import info.nightscout.rx.events.EventMobileToWear import info.nightscout.rx.events.EventMobileToWear
import info.nightscout.rx.events.EventNewBG
import info.nightscout.rx.events.EventNewOpenLoopNotification import info.nightscout.rx.events.EventNewOpenLoopNotification
import info.nightscout.rx.events.EventPreferenceChange import info.nightscout.rx.events.EventPreferenceChange
import info.nightscout.rx.events.EventPumpStatusChanged import info.nightscout.rx.events.EventPumpStatusChanged
@ -99,6 +99,7 @@ import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.weardata.EventData import info.nightscout.rx.weardata.EventData
import info.nightscout.shared.extensions.runOnUiThread import info.nightscout.shared.extensions.runOnUiThread
import info.nightscout.shared.extensions.toVisibility import info.nightscout.shared.extensions.toVisibility
import info.nightscout.shared.extensions.toVisibilityKeepSpace
import info.nightscout.shared.interfaces.ResourceHelper import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil import info.nightscout.shared.utils.DateUtil
@ -276,7 +277,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
sp.putBoolean(R.string.key_objectiveusescale, true) sp.putBoolean(R.string.key_objectiveusescale, true)
}, fabricPrivacy::logException) }, fabricPrivacy::logException)
disposable += rxBus disposable += rxBus
.toObservable(EventNewBG::class.java) .toObservable(EventBucketedDataCreated::class.java)
.debounce(1L, TimeUnit.SECONDS) .debounce(1L, TimeUnit.SECONDS)
.observeOn(aapsSchedulers.io) .observeOn(aapsSchedulers.io)
.subscribe({ updateBg() }, fabricPrivacy::logException) .subscribe({ updateBg() }, fabricPrivacy::logException)
@ -778,19 +779,19 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
fun updateBg() { fun updateBg() {
val units = profileFunction.getUnits() val units = profileFunction.getUnits()
val lastBg = overviewData.lastBg val lastBg = overviewData.lastBg(iobCobCalculator.ads)
val lastBgColor = overviewData.lastBgColor(context) val lastBgColor = overviewData.lastBgColor(context, iobCobCalculator.ads)
val isActualBg = overviewData.isActualBg val isActualBg = overviewData.isActualBg(iobCobCalculator.ads)
val glucoseStatus = glucoseStatusProvider.glucoseStatusData val glucoseStatus = glucoseStatusProvider.glucoseStatusData
val trendDescription = trendCalculator.getTrendDescription(lastBg) val trendDescription = trendCalculator.getTrendDescription(iobCobCalculator.ads)
val trendArrow = trendCalculator.getTrendArrow(lastBg) val trendArrow = trendCalculator.getTrendArrow(iobCobCalculator.ads)
val lastBgDescription = overviewData.lastBgDescription val lastBgDescription = overviewData.lastBgDescription(iobCobCalculator.ads)
runOnUiThread { runOnUiThread {
_binding ?: return@runOnUiThread _binding ?: return@runOnUiThread
binding.infoLayout.bg.text = lastBg?.valueToUnitsString(units) binding.infoLayout.bg.text = lastBg?.valueToUnitsString(units) ?: ""
?: rh.gs(info.nightscout.core.ui.R.string.value_unavailable_short)
binding.infoLayout.bg.setTextColor(lastBgColor) binding.infoLayout.bg.setTextColor(lastBgColor)
binding.infoLayout.arrow.setImageResource(trendArrow.directionToIcon()) trendArrow?.let { binding.infoLayout.arrow.setImageResource(it.directionToIcon()) }
binding.infoLayout.arrow.visibility = (trendArrow != null).toVisibilityKeepSpace()
binding.infoLayout.arrow.setColorFilter(lastBgColor) binding.infoLayout.arrow.setColorFilter(lastBgColor)
binding.infoLayout.arrow.contentDescription = lastBgDescription + " " + rh.gs(info.nightscout.core.ui.R.string.and) + " " + trendDescription binding.infoLayout.arrow.contentDescription = lastBgDescription + " " + rh.gs(info.nightscout.core.ui.R.string.and) + " " + trendDescription

View file

@ -155,7 +155,7 @@ class IobCobCalculatorPlugin @Inject constructor(
overviewData = overviewData, overviewData = overviewData,
reason = reason, reason = reason,
end = System.currentTimeMillis(), end = System.currentTimeMillis(),
bgDataReload = false, bgDataReload = true,
cause = event cause = event
) )
} }

View file

@ -237,7 +237,7 @@ class AutosensDataStoreObject : AutosensDataStore {
val bgDelta = newer.value - older.value val bgDelta = newer.value - older.value
val timeDiffToNew = newer.timestamp - currentTime val timeDiffToNew = newer.timestamp - currentTime
val currentBg = newer.value - timeDiffToNew.toDouble() / (newer.timestamp - older.timestamp) * bgDelta val currentBg = newer.value - timeDiffToNew.toDouble() / (newer.timestamp - older.timestamp) * bgDelta
val newBgReading = InMemoryGlucoseValue(currentTime, currentBg.roundToLong().toDouble(), true) val newBgReading = InMemoryGlucoseValue(currentTime, currentBg.roundToLong().toDouble())
newBucketedData.add(newBgReading) newBucketedData.add(newBgReading)
//log.debug("BG: " + newBgReading.value + " (" + new Date(newBgReading.date).toLocaleString() + ") Prev: " + older.value + " (" + new Date(older.date).toLocaleString() + ") Newer: " + newer.value + " (" + new Date(newer.date).toLocaleString() + ")"); //log.debug("BG: " + newBgReading.value + " (" + new Date(newBgReading.date).toLocaleString() + ") Prev: " + older.value + " (" + new Date(older.date).toLocaleString() + ") Newer: " + newer.value + " (" + new Date(newer.date).toLocaleString() + ")");
} }
@ -273,7 +273,7 @@ class AutosensDataStoreObject : AutosensDataStore {
val gapDelta = bgReadings[i].value - lastBg val gapDelta = bgReadings[i].value - lastBg
//console.error(gapDelta, lastBg, elapsed_minutes); //console.error(gapDelta, lastBg, elapsed_minutes);
val nextBg = lastBg + 5.0 / elapsedMinutes * gapDelta val nextBg = lastBg + 5.0 / elapsedMinutes * gapDelta
val newBgReading = InMemoryGlucoseValue(nextBgTime, nextBg.roundToLong().toDouble(), true) val newBgReading = InMemoryGlucoseValue(nextBgTime, nextBg.roundToLong().toDouble())
//console.error("Interpolated", bData[j]); //console.error("Interpolated", bData[j]);
bData.add(newBgReading) bData.add(newBgReading)
aapsLogger.debug(LTag.AUTOSENS) { "Adding. bgTime: ${dateUtil.toISOString(bgTime)} lastBgTime: ${dateUtil.toISOString(lastBgTime)} $newBgReading" } aapsLogger.debug(LTag.AUTOSENS) { "Adding. bgTime: ${dateUtil.toISOString(bgTime)} lastBgTime: ${dateUtil.toISOString(lastBgTime)} $newBgReading" }

View file

@ -24,6 +24,7 @@ import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.T import info.nightscout.shared.utils.T
import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.kotlin.plusAssign
import java.lang.Math.random
import java.util.Calendar import java.util.Calendar
import java.util.GregorianCalendar import java.util.GregorianCalendar
import javax.inject.Inject import javax.inject.Inject
@ -102,7 +103,7 @@ class RandomBgPlugin @Inject constructor(
val cal = GregorianCalendar() val cal = GregorianCalendar()
val currentMinute = cal[Calendar.MINUTE] + (cal[Calendar.HOUR_OF_DAY] % 2) * 60 val currentMinute = cal[Calendar.MINUTE] + (cal[Calendar.HOUR_OF_DAY] % 2) * 60
val bgMgdl = min + ((max - min) + (max - min) * sin(currentMinute / period * 2 * PI)) / 2 val bgMgdl = min + ((max - min) + (max - min) * sin(currentMinute / period * 2 * PI)) / 2 + (random() - 0.5) * (max - min) * 0.4
cal[Calendar.MILLISECOND] = 0 cal[Calendar.MILLISECOND] = 0
cal[Calendar.SECOND] = 0 cal[Calendar.SECOND] = 0

View file

@ -5,7 +5,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical" android:orientation="vertical"
tools:context="info.nightscout.androidaps.plugins.general.overview.OverviewFragment"> tools:context=".general.overview.OverviewFragment">
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:id="@+id/top_part_scrollbar" android:id="@+id/top_part_scrollbar"

View file

@ -11,7 +11,7 @@
android:id="@+id/bg" android:id="@+id/bg"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="00.0" tools:text="00.0"
android:textSize="60sp" android:textSize="60sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@+id/delta_large" app:layout_constraintEnd_toStartOf="@+id/delta_large"
@ -32,7 +32,7 @@
android:id="@+id/delta_large" android:id="@+id/delta_large"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="(+1.0)" tools:text="(+1.0)"
android:textSize="60sp" android:textSize="60sp"
android:textStyle="bold" android:textStyle="bold"
android:visibility="gone" android:visibility="gone"
@ -60,7 +60,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:srcCompat="@drawable/ic_flat"
android:contentDescription="@string/trend_arrow" /> android:contentDescription="@string/trend_arrow" />
<TextView <TextView
@ -69,7 +68,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
app:layout_constraintTop_toTopOf="@+id/long_avg_delta" app:layout_constraintTop_toTopOf="@+id/long_avg_delta"
android:text="n/a" tools:text="n/a"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textAppearance="@style/TextAppearance.AppCompat.Small"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
@ -106,7 +105,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end" android:layout_gravity="end"
android:textAlignment="textEnd" android:textAlignment="textEnd"
android:text="n/a" tools:text="n/a"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textAppearance="@style/TextAppearance.AppCompat.Small"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
@ -123,7 +122,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end" android:layout_gravity="end"
android:textAlignment="textEnd" android:textAlignment="textEnd"
android:text="15m Δ: " android:text="Δ15: "
android:contentDescription="15 minutes delta" android:contentDescription="15 minutes delta"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textAppearance="@style/TextAppearance.AppCompat.Small"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
@ -134,7 +133,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end" android:layout_gravity="end"
android:textAlignment="textEnd" android:textAlignment="textEnd"
android:text="n/a" tools:text="n/a"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textAppearance="@style/TextAppearance.AppCompat.Small"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
@ -151,7 +150,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end" android:layout_gravity="end"
android:textAlignment="textEnd" android:textAlignment="textEnd"
android:text="40m Δ: " android:text="Δ40: "
android:contentDescription="40 minutes delta" android:contentDescription="40 minutes delta"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textAppearance="@style/TextAppearance.AppCompat.Small"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
@ -162,7 +161,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end" android:layout_gravity="end"
android:textAlignment="textEnd" android:textAlignment="textEnd"
android:text="n/a" tools:text="n/a"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textAppearance="@style/TextAppearance.AppCompat.Small"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
@ -226,7 +225,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:layout_marginTop="-5dp" android:layout_marginTop="-5dp"
android:text="8:00 PM" tools:text="8:00 PM"
android:textSize="25sp" android:textSize="25sp"
android:textStyle="bold" android:textStyle="bold"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
@ -238,7 +237,7 @@
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:layout_marginTop="-10dp" android:layout_marginTop="-10dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:text="(-5)" tools:text="(-5)"
android:textSize="19sp" android:textSize="19sp"
android:textStyle="bold" android:textStyle="bold"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
@ -271,7 +270,7 @@
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:paddingTop="3dp" android:paddingTop="3dp"
android:paddingBottom="3dp" android:paddingBottom="3dp"
android:text="n/a" tools:text="n/a"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textStyle="bold" android:textStyle="bold"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
@ -304,7 +303,7 @@
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:paddingTop="3dp" android:paddingTop="3dp"
android:paddingBottom="3dp" android:paddingBottom="3dp"
android:text="n/a" tools:text="n/a"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textStyle="bold" android:textStyle="bold"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
@ -336,7 +335,7 @@
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:paddingTop="3dp" android:paddingTop="3dp"
android:paddingBottom="3dp" android:paddingBottom="3dp"
android:text="n/a" tools:text="n/a"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textStyle="bold" android:textStyle="bold"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
@ -367,7 +366,7 @@
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:paddingTop="3dp" android:paddingTop="3dp"
android:paddingBottom="3dp" android:paddingBottom="3dp"
android:text="n/a" tools:text="n/a"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textStyle="bold" android:textStyle="bold"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
@ -400,7 +399,7 @@
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:paddingTop="3dp" android:paddingTop="3dp"
android:paddingBottom="3dp" android:paddingBottom="3dp"
android:text="n/a" tools:text="n/a"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textStyle="bold" android:textStyle="bold"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
@ -412,7 +411,7 @@
android:layout_marginTop="-9dp" android:layout_marginTop="-9dp"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:paddingBottom="3dp" android:paddingBottom="3dp"
android:text="n/a" tools:text="n/a"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textStyle="bold" android:textStyle="bold"
android:visibility="visible" android:visibility="visible"

View file

@ -18,7 +18,6 @@ android {
dependencies { dependencies {
implementation project(':app-wear-shared:shared') implementation project(':app-wear-shared:shared')
implementation project(':database:entities')
implementation project(':core:interfaces') implementation project(':core:interfaces')
implementation project(':core:ui') implementation project(':core:ui')
} }

View file

@ -2,7 +2,7 @@ package info.nightscout.smoothing
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.annotations.OpenForTesting import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.interfaces.plugin.ActivePlugin import info.nightscout.interfaces.iob.InMemoryGlucoseValue
import info.nightscout.interfaces.plugin.PluginBase import info.nightscout.interfaces.plugin.PluginBase
import info.nightscout.interfaces.plugin.PluginDescription import info.nightscout.interfaces.plugin.PluginDescription
import info.nightscout.interfaces.plugin.PluginType import info.nightscout.interfaces.plugin.PluginType
@ -11,14 +11,15 @@ import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.shared.interfaces.ResourceHelper import info.nightscout.shared.interfaces.ResourceHelper
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
import kotlin.math.max
import kotlin.math.round
@OpenForTesting @OpenForTesting
@Singleton @Singleton
class ExponentialSmoothingPlugin @Inject constructor( class ExponentialSmoothingPlugin @Inject constructor(
injector: HasAndroidInjector, injector: HasAndroidInjector,
aapsLogger: AAPSLogger, aapsLogger: AAPSLogger,
rh: ResourceHelper, rh: ResourceHelper
activePlugin: ActivePlugin,
) : PluginBase( ) : PluginBase(
PluginDescription() PluginDescription()
.mainType(PluginType.SMOOTHING) .mainType(PluginType.SMOOTHING)
@ -28,4 +29,113 @@ class ExponentialSmoothingPlugin @Inject constructor(
.description(R.string.description_exponential_smoothing), .description(R.string.description_exponential_smoothing),
aapsLogger, rh, injector aapsLogger, rh, injector
), Smoothing { ), Smoothing {
@Suppress("LocalVariableName")
override fun smooth(data: MutableList<InMemoryGlucoseValue>): MutableList<InMemoryGlucoseValue> {
/**
* TSUNAMI DATA SMOOTHING CORE
*
* Calculated a weighted average of 1st and 2nd order exponential smoothing functions
* to reduce the effect of sensor noise on APS performance. The weighted average
* is a compromise between the fast response to changing BGs at the cost of smoothness
* as offered by 1st order exponential smoothing, and the predictive, trend-sensitive but
* slower-to-respond smoothing as offered by 2nd order functions.
*
*/
val sizeRecords = data.size
val o1_sBG: ArrayList<Double> = ArrayList() //MP array for 1st order Smoothed Blood Glucose
val o2_sBG: ArrayList<Double> = ArrayList() //MP array for 2nd order Smoothed Blood Glucose
val o2_sD: ArrayList<Double> = ArrayList() //MP array for 2nd order Smoothed delta
val ssBG: ArrayList<Double> = ArrayList() //MP array for weighted averaged, doubly smoothed Blood Glucose
//val ssD: ArrayList<Double> = ArrayList() //MP array for deltas of doubly smoothed Blood Glucose
var windowSize = data.size //MP number of bg readings to include in smoothing window
val o1_weight = 0.4
val o1_a = 0.5
val o2_a = 0.4
val o2_b = 1.0
var insufficientSmoothingData = false
// ADJUST SMOOTHING WINDOW TO ONLY INCLUDE VALID READINGS
// Valid readings include:
// - Values that actually exist (windowSize may not be larger than sizeRecords)
// - Values that come in approx. every 5 min. If the time gap between two readings is larger, this is likely due to a sensor error or warmup of a new sensor.d
// - Values that are not 38 mg/dl; 38 mg/dl reflects an xDrip error state (according to a comment in determine-basal.js)
//MP: Adjust smoothing window if database size is smaller than the default value + 1 (+1 because the reading before the oldest reading to be smoothed will be used in the calculations
if (sizeRecords <= windowSize) { //MP standard smoothing window
windowSize =
(sizeRecords - 1).coerceAtLeast(0) //MP Adjust smoothing window to the size of database if it is smaller than the original window size; -1 to always have at least one older value to compare against as a buffer to prevent app crashes
}
//MP: Adjust smoothing window further if a gap in the BG database is detected, e.g. due to sensor errors of sensor swaps, or if 38 mg/dl are reported (xDrip error state)
for (i in 0 until windowSize) {
if (round((data[i].timestamp - data[i + 1].timestamp) / (1000.0 * 60)) >= 12) { //MP: 12 min because a missed reading (i.e. readings coming in after 10 min) can occur for various reasons, like walking away from the phone or reinstalling AAPS
//if (Math.round((data.get(i).date - data.get(i + 1).date) / 60000L) <= 7) { //MP crashes the app, useful for testing
windowSize =
i + 1 //MP: If time difference between two readings exceeds 7 min, adjust windowSize to *include* the more recent reading (i = reading; +1 because windowSize reflects number of valid readings);
break
} else if (data[i].value == 38.0) {
windowSize = i //MP: 38 mg/dl reflects an xDrip error state; Chain of valid readings ends here, *exclude* this value (windowSize = i; i + 1 would include the current value)
break
}
}
// CALCULATE SMOOTHING WINDOW - 1st order exponential smoothing
o1_sBG.clear() // MP reset smoothed bg array
if (windowSize >= 4) { //MP: Require a valid windowSize of at least 4 readings
o1_sBG.add(data[windowSize - 1].value) //MP: Initialise smoothing with the oldest valid data point
for (i in 0 until windowSize) { //MP calculate smoothed bg window of valid readings
o1_sBG.add(
0,
o1_sBG[0] + o1_a * (data[windowSize - 1 - i].value - o1_sBG[0])
) //MP build array of 1st order smoothed bgs
}
} else {
insufficientSmoothingData = true
}
// CALCULATE SMOOTHING WINDOW - 2nd order exponential smoothing
if (windowSize >= 4) { //MP: Require a valid windowSize of at least 4 readings
o2_sBG.add(data[windowSize - 1].value) //MP Start 2nd order exponential data smoothing with the oldest valid bg
o2_sD.add(data[windowSize - 2].value - data[windowSize - 1].value) //MP Start 2nd order exponential data smoothing with the oldest valid delta
for (i in 0 until windowSize - 1) { //MP calculated smoothed bg window of last 1 h
o2_sBG.add(
0,
o2_a * data[windowSize - 2 - i].value + (1 - o2_a) * (o2_sBG[0] + o2_sD[0])
) //MP build array of 2nd order smoothed bgs; windowSize-1 is the oldest valid bg value, so windowSize-2 is from when on the smoothing begins;
o2_sD.add(
0,
o2_b * (o2_sBG[0] - o2_sBG[1]) + (1 - o2_b) * o2_sD[0]
) //MP build array of 1st order smoothed bgs
}
} else {
insufficientSmoothingData = true
}
// CALCULATE WEIGHTED AVERAGES OF GLUCOSE & DELTAS
//ssBG.clear() // MP reset doubly smoothed bg array
//ssD.clear() // MP reset doubly smoothed delta array
if (!insufficientSmoothingData) { //MP Build doubly smoothed array only if there is enough valid readings
for (i in o2_sBG.indices) { //MP calculated doubly smoothed bg of all o1/o2 smoothed data available; o2 & o1 smoothbg array sizes are equal in size, so only one is used as a condition here
ssBG.add(o1_weight * o1_sBG[i] + (1 - o1_weight) * o2_sBG[i]) //MP build array of doubly smoothed bgs
}
/*
for (i in 0 until ssBG.size - 1) {
ssD.add(ssBG[i] - ssBG[i + 1]) //MP build array of doubly smoothed bg deltas
}
*/
for (i in 0 until minOf(ssBG.size, data.size)) { // noise at the beginning of the smoothing window is the greatest, so only include the 10 most recent values in the output
data[i].smoothed = max(round(ssBG[i]), 39.0) //Make 39 the smallest value as smaller values trigger errors (xDrip error state = 38)
}
} else {
for (i in 0 until data.size) { // noise at the beginning of the smoothing window is the greatest, so only include the 10 most recent values in the output
data[i].smoothed = max(data[i].value, 39.0) // if insufficient smoothing data, copy 'value' into 'smoothed' data column so that it isn't empty; Make 39 the smallest value as smaller
// values trigger errors (xDrip error state = 38)
}
}
return data
}
} }

View file

@ -2,7 +2,7 @@ package info.nightscout.smoothing
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.annotations.OpenForTesting import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.interfaces.plugin.ActivePlugin import info.nightscout.interfaces.iob.InMemoryGlucoseValue
import info.nightscout.interfaces.plugin.PluginBase import info.nightscout.interfaces.plugin.PluginBase
import info.nightscout.interfaces.plugin.PluginDescription import info.nightscout.interfaces.plugin.PluginDescription
import info.nightscout.interfaces.plugin.PluginType import info.nightscout.interfaces.plugin.PluginType
@ -22,10 +22,12 @@ class NoSmoothingPlugin @Inject constructor(
PluginDescription() PluginDescription()
.mainType(PluginType.SMOOTHING) .mainType(PluginType.SMOOTHING)
.pluginIcon(info.nightscout.core.ui.R.drawable.ic_timeline_24) .pluginIcon(info.nightscout.core.ui.R.drawable.ic_timeline_24)
.enableByDefault(true) .setDefault(true)
.pluginName(R.string.no_smoothing_name) .pluginName(R.string.no_smoothing_name)
.shortName(R.string.smoothing_shortname) .shortName(R.string.smoothing_shortname)
.description(R.string.description_no_smoothing), .description(R.string.description_no_smoothing),
aapsLogger, rh, injector aapsLogger, rh, injector
), Smoothing { ), Smoothing {
override fun smooth(data: MutableList<InMemoryGlucoseValue>): MutableList<InMemoryGlucoseValue> = data
} }

View file

@ -35,6 +35,7 @@ import info.nightscout.interfaces.utils.TrendCalculator
import info.nightscout.rx.logging.AAPSLogger import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag import info.nightscout.rx.logging.LTag
import info.nightscout.shared.extensions.toVisibility import info.nightscout.shared.extensions.toVisibility
import info.nightscout.shared.extensions.toVisibilityKeepSpace
import info.nightscout.shared.interfaces.ResourceHelper import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil import info.nightscout.shared.utils.DateUtil
@ -125,20 +126,23 @@ class Widget : AppWidgetProvider() {
private fun updateBg(views: RemoteViews) { private fun updateBg(views: RemoteViews) {
val units = profileFunction.getUnits() val units = profileFunction.getUnits()
views.setTextViewText(R.id.bg, overviewData.lastBg?.valueToUnitsString(units) ?: rh.gs(info.nightscout.core.ui.R.string.value_unavailable_short)) views.setTextViewText(R.id.bg, overviewData.lastBg(iobCobCalculator.ads)?.valueToUnitsString(units) ?: rh.gs(info.nightscout.core.ui.R.string.value_unavailable_short))
views.setTextColor( views.setTextColor(
R.id.bg, when { R.id.bg, when {
overviewData.isLow -> rh.gc(info.nightscout.core.ui.R.color.widget_low) overviewData.isLow(iobCobCalculator.ads) -> rh.gc(info.nightscout.core.ui.R.color.widget_low)
overviewData.isHigh -> rh.gc(info.nightscout.core.ui.R.color.widget_high) overviewData.isHigh(iobCobCalculator.ads) -> rh.gc(info.nightscout.core.ui.R.color.widget_high)
else -> rh.gc(info.nightscout.core.ui.R.color.widget_inrange) else -> rh.gc(info.nightscout.core.ui.R.color.widget_inrange)
} }
) )
views.setImageViewResource(R.id.arrow, trendCalculator.getTrendArrow(overviewData.lastBg).directionToIcon()) trendCalculator.getTrendArrow(iobCobCalculator.ads)?.let {
views.setImageViewResource(R.id.arrow, it.directionToIcon())
}
views.setViewVisibility(R.id.arrow, (trendCalculator.getTrendArrow(iobCobCalculator.ads) != null).toVisibilityKeepSpace())
views.setInt( views.setInt(
R.id.arrow, "setColorFilter", when { R.id.arrow, "setColorFilter", when {
overviewData.isLow -> rh.gc(info.nightscout.core.ui.R.color.widget_low) overviewData.isLow(iobCobCalculator.ads) -> rh.gc(info.nightscout.core.ui.R.color.widget_low)
overviewData.isHigh -> rh.gc(info.nightscout.core.ui.R.color.widget_high) overviewData.isHigh(iobCobCalculator.ads) -> rh.gc(info.nightscout.core.ui.R.color.widget_high)
else -> rh.gc(info.nightscout.core.ui.R.color.widget_inrange) else -> rh.gc(info.nightscout.core.ui.R.color.widget_inrange)
} }
) )
@ -154,10 +158,10 @@ class Widget : AppWidgetProvider() {
} }
// strike through if BG is old // strike through if BG is old
if (!overviewData.isActualBg) views.setInt(R.id.bg, "setPaintFlags", Paint.STRIKE_THRU_TEXT_FLAG or Paint.ANTI_ALIAS_FLAG) if (!overviewData.isActualBg(iobCobCalculator.ads)) views.setInt(R.id.bg, "setPaintFlags", Paint.STRIKE_THRU_TEXT_FLAG or Paint.ANTI_ALIAS_FLAG)
else views.setInt(R.id.bg, "setPaintFlags", Paint.ANTI_ALIAS_FLAG) else views.setInt(R.id.bg, "setPaintFlags", Paint.ANTI_ALIAS_FLAG)
views.setTextViewText(R.id.time_ago, dateUtil.minAgo(rh, overviewData.lastBg?.timestamp)) views.setTextViewText(R.id.time_ago, dateUtil.minAgo(rh, overviewData.lastBg(iobCobCalculator.ads)?.timestamp))
//views.setTextViewText(R.id.time_ago_short, "(" + dateUtil.minAgoShort(overviewData.lastBg?.timestamp) + ")") //views.setTextViewText(R.id.time_ago_short, "(" + dateUtil.minAgoShort(overviewData.lastBg?.timestamp) + ")")
} }

View file

@ -8,6 +8,7 @@ import info.nightscout.core.utils.worker.LoggingWorker
import info.nightscout.database.impl.AppRepository import info.nightscout.database.impl.AppRepository
import info.nightscout.interfaces.aps.AutosensDataStore import info.nightscout.interfaces.aps.AutosensDataStore
import info.nightscout.interfaces.iob.IobCobCalculator import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.rx.bus.RxBus import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventBucketedDataCreated import info.nightscout.rx.events.EventBucketedDataCreated
import info.nightscout.rx.logging.AAPSLogger import info.nightscout.rx.logging.AAPSLogger
@ -25,6 +26,7 @@ class LoadBgDataWorker(
@Inject lateinit var dateUtil: DateUtil @Inject lateinit var dateUtil: DateUtil
@Inject lateinit var rxBus: RxBus @Inject lateinit var rxBus: RxBus
@Inject lateinit var repository: AppRepository @Inject lateinit var repository: AppRepository
@Inject lateinit var activePlugin: ActivePlugin
class LoadBgData( class LoadBgData(
val iobCobCalculator: IobCobCalculator, val iobCobCalculator: IobCobCalculator,
@ -32,7 +34,7 @@ class LoadBgDataWorker(
) )
private fun AutosensDataStore.loadBgData(to: Long, repository: AppRepository, aapsLogger: AAPSLogger, dateUtil: DateUtil, rxBus: RxBus) { private fun AutosensDataStore.loadBgData(to: Long, repository: AppRepository, aapsLogger: AAPSLogger, dateUtil: DateUtil) {
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)
@ -43,7 +45,15 @@ class LoadBgDataWorker(
.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()) }
}
private fun AutosensDataStore.smoothData(activePlugin: ActivePlugin) {
synchronized(dataLock) {
bucketedData?.let {
val smoothedData = activePlugin.activeSmoothing.smooth(it)
bucketedData = smoothedData
}
} }
} }
@ -52,7 +62,9 @@ class LoadBgDataWorker(
val data = dataWorkerStorage.pickupObject(inputData.getLong(DataWorkerStorage.STORE_KEY, -1)) as LoadBgData? val data = dataWorkerStorage.pickupObject(inputData.getLong(DataWorkerStorage.STORE_KEY, -1)) as LoadBgData?
?: return Result.failure(workDataOf("Error" to "missing input data")) ?: return Result.failure(workDataOf("Error" to "missing input data"))
data.iobCobCalculator.ads.loadBgData(data.end, repository, aapsLogger, dateUtil, rxBus) data.iobCobCalculator.ads.loadBgData(data.end, repository, aapsLogger, dateUtil)
data.iobCobCalculator.ads.smoothData(activePlugin)
rxBus.send(EventBucketedDataCreated())
data.iobCobCalculator.clearCache() data.iobCobCalculator.clearCache()
return Result.success() return Result.success()
} }

View file

@ -10,6 +10,7 @@ import info.nightscout.core.graph.data.PointsWithLabelGraphSeries
import info.nightscout.core.utils.receivers.DataWorkerStorage import info.nightscout.core.utils.receivers.DataWorkerStorage
import info.nightscout.core.utils.worker.LoggingWorker import info.nightscout.core.utils.worker.LoggingWorker
import info.nightscout.interfaces.iob.IobCobCalculator import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.profile.DefaultValueHelper
import info.nightscout.interfaces.profile.ProfileFunction import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.shared.interfaces.ResourceHelper import info.nightscout.shared.interfaces.ResourceHelper
import javax.inject.Inject import javax.inject.Inject
@ -22,6 +23,7 @@ class PrepareBucketedDataWorker(
@Inject lateinit var dataWorkerStorage: DataWorkerStorage @Inject lateinit var dataWorkerStorage: DataWorkerStorage
@Inject lateinit var profileFunction: ProfileFunction @Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var rh: ResourceHelper @Inject lateinit var rh: ResourceHelper
@Inject lateinit var defaultValueHelper: DefaultValueHelper
class PrepareBucketedData( class PrepareBucketedData(
val iobCobCalculator: IobCobCalculator, // cannot be injected : HistoryBrowser uses different instance val iobCobCalculator: IobCobCalculator, // cannot be injected : HistoryBrowser uses different instance
@ -41,7 +43,7 @@ class PrepareBucketedDataWorker(
val bucketedListArray: MutableList<DataPointWithLabelInterface> = ArrayList() val bucketedListArray: MutableList<DataPointWithLabelInterface> = ArrayList()
for (inMemoryGlucoseValue in bucketedData) { for (inMemoryGlucoseValue in bucketedData) {
if (inMemoryGlucoseValue.timestamp < data.overviewData.fromTime || inMemoryGlucoseValue.timestamp > data.overviewData.toTime) continue if (inMemoryGlucoseValue.timestamp < data.overviewData.fromTime || inMemoryGlucoseValue.timestamp > data.overviewData.toTime) continue
bucketedListArray.add(InMemoryGlucoseValueDataPoint(inMemoryGlucoseValue, profileFunction, rh)) bucketedListArray.add(InMemoryGlucoseValueDataPoint(inMemoryGlucoseValue, defaultValueHelper , profileFunction, rh))
} }
bucketedListArray.sortWith { o1: DataPointWithLabelInterface, o2: DataPointWithLabelInterface -> o1.x.compareTo(o2.x) } bucketedListArray.sortWith { o1: DataPointWithLabelInterface, o2: DataPointWithLabelInterface -> o1.x.compareTo(o2.x) }
data.overviewData.bucketedGraphSeries = PointsWithLabelGraphSeries(Array(bucketedListArray.size) { i -> bucketedListArray[i] }) data.overviewData.bucketedGraphSeries = PointsWithLabelGraphSeries(Array(bucketedListArray.size) { i -> bucketedListArray[i] })

View file

@ -85,6 +85,7 @@ class PrepareIobAutosensGraphDataWorker(
override val duration = 0L override val duration = 0L
override val shape = PointsWithLabelGraphSeries.Shape.IOB_PREDICTION override val shape = PointsWithLabelGraphSeries.Shape.IOB_PREDICTION
override val size = 0.5f override val size = 0.5f
override val paintStyle: Paint.Style = Paint.Style.FILL
override fun color(context: Context?): Int = color override fun color(context: Context?): Int = color
fun setColor(color: Int): IobTotalDataPoint { fun setColor(color: Int): IobTotalDataPoint {
@ -107,6 +108,7 @@ class PrepareIobAutosensGraphDataWorker(
override val duration = 0L override val duration = 0L
override val shape = PointsWithLabelGraphSeries.Shape.COB_FAIL_OVER override val shape = PointsWithLabelGraphSeries.Shape.COB_FAIL_OVER
override val size = 0.5f override val size = 0.5f
override val paintStyle: Paint.Style = Paint.Style.FILL
override fun color(context: Context?): Int { override fun color(context: Context?): Int {
return rh.gac(context, info.nightscout.core.ui.R.attr.cobColor) return rh.gac(context, info.nightscout.core.ui.R.attr.cobColor)
} }

View file

@ -122,14 +122,14 @@ class IobCobOref1Worker(
//console.error(bgTime , bucketed_data[i].glucose); //console.error(bgTime , bucketed_data[i].glucose);
var avgDelta: Double var avgDelta: Double
var delta: Double var delta: Double
val bg: Double = bucketedData[i].value val bg: Double = bucketedData[i].recalculated
if (bg < 39 || bucketedData[i + 3].value < 39) { if (bg < 39 || bucketedData[i + 3].recalculated < 39) {
aapsLogger.error("! value < 39") aapsLogger.error("! value < 39")
continue continue
} }
autosensData.bg = bg autosensData.bg = bg
delta = bg - bucketedData[i + 1].value delta = bg - bucketedData[i + 1].recalculated
avgDelta = (bg - bucketedData[i + 3].value) / 3 avgDelta = (bg - bucketedData[i + 3].recalculated) / 3
val iob = data.iobCobCalculator.calculateFromTreatmentsAndTemps(bgTime, profile) val iob = data.iobCobCalculator.calculateFromTreatmentsAndTemps(bgTime, profile)
val bgi = -iob.activity * sens * 5 val bgi = -iob.activity * sens * 5
val deviation = delta - bgi val deviation = delta - bgi

View file

@ -117,14 +117,14 @@ class IobCobOrefWorker @Inject internal constructor(
//console.error(bgTime , bucketed_data[i].glucose); //console.error(bgTime , bucketed_data[i].glucose);
var avgDelta: Double var avgDelta: Double
var delta: Double var delta: Double
val bg: Double = bucketedData[i].value val bg: Double = bucketedData[i].recalculated
if (bg < 39 || bucketedData[i + 3].value < 39) { if (bg < 39 || bucketedData[i + 3].recalculated < 39) {
aapsLogger.error("! value < 39") aapsLogger.error("! value < 39")
continue continue
} }
autosensData.bg = bg autosensData.bg = bg
delta = bg - bucketedData[i + 1].value delta = bg - bucketedData[i + 1].recalculated
avgDelta = (bg - bucketedData[i + 3].value) / 3 avgDelta = (bg - bucketedData[i + 3].recalculated) / 3
val iob = data.iobCobCalculatorPlugin.calculateFromTreatmentsAndTemps(bgTime, profile) val iob = data.iobCobCalculatorPlugin.calculateFromTreatmentsAndTemps(bgTime, profile)
val bgi = -iob.activity * sens * 5 val bgi = -iob.activity * sens * 5
val deviation = delta - bgi val deviation = delta - bgi