diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt index b7b0e32dc9..2a14115056 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt @@ -15,6 +15,7 @@ import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin import info.nightscout.androidaps.plugins.aps.openAPSAMA.OpenAPSAMAPlugin import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin +import info.nightscout.androidaps.plugins.constraints.bgQualityCheck.BgQualityCheckPlugin import info.nightscout.androidaps.plugins.constraints.dstHelper.DstHelperPlugin import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin @@ -294,6 +295,12 @@ abstract class PluginsModule { @IntKey(380) abstract fun bindDstHelperPlugin(plugin: DstHelperPlugin): PluginBase + @Binds + @AllConfigs + @IntoMap + @IntKey(381) + abstract fun bindBgQualityCheckPlugin(plugin: BgQualityCheckPlugin): PluginBase + @Binds @AllConfigs @IntoMap diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/bgQualityCheck/BgQualityCheckPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/bgQualityCheck/BgQualityCheckPlugin.kt new file mode 100644 index 0000000000..4417662f52 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/bgQualityCheck/BgQualityCheckPlugin.kt @@ -0,0 +1,93 @@ +package info.nightscout.androidaps.plugins.constraints.bgQualityCheck + +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.R +import info.nightscout.androidaps.interfaces.* +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventBucketedDataCreated +import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.rx.AapsSchedulers +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.rxkotlin.plusAssign +import java.util.* +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.math.abs + +@Singleton +class BgQualityCheckPlugin @Inject constructor( + injector: HasAndroidInjector, + aapsLogger: AAPSLogger, + rh: ResourceHelper, + private val rxBus: RxBus, + private val iobCobCalculator: IobCobCalculator, + private val aapsSchedulers: AapsSchedulers, + private val fabricPrivacy: FabricPrivacy +) : PluginBase( + PluginDescription() + .mainType(PluginType.CONSTRAINTS) + .neverVisible(true) + .alwaysEnabled(true) + .showInList(false) + .pluginName(R.string.dst_plugin_name), + aapsLogger, rh, injector +), Constraints { + + private var disposable: CompositeDisposable = CompositeDisposable() + + enum class State { + UNKNOWN, + FIVE_MIN_DATA, + RECALCULATED, + DOUBLED + } + + override fun onStart() { + super.onStart() + disposable += rxBus + .toObservable(EventBucketedDataCreated::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ processBgData() }, fabricPrivacy::logException) + } + + override fun onStop() { + super.onStop() + disposable.clear() + } + + var state: State = State.UNKNOWN + + // Return false if BG values are doubled + @Suppress("ReplaceGetOrSet") + override fun isLoopInvocationAllowed(value: Constraint): Constraint { + if (state == State.DOUBLED) + value.set(aapsLogger, false, "Doubled values in BGSource", this) + return value + } + + fun processBgData() { + val readings = iobCobCalculator.ads.getBgReadingsDataTableCopy() + for (i in readings.indices) + if (i < readings.size - 2) + if (abs(readings[i].timestamp - readings[i + 1].timestamp) <= 1000) { + state = State.DOUBLED + return + } + if (iobCobCalculator.ads.lastUsed5minCalculation == true) + state = State.FIVE_MIN_DATA + else if (iobCobCalculator.ads.lastUsed5minCalculation == false) + state = State.RECALCULATED + else + state = State.UNKNOWN + } + + fun icon(): Int = + when (state) { + State.UNKNOWN -> 0 + State.FIVE_MIN_DATA -> 0 + State.RECALCULATED -> R.drawable.ic_baseline_warning_24_yellow + State.DOUBLED -> R.drawable.ic_baseline_warning_24_red + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/dstHelper/DstHelperPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/dstHelper/DstHelperPlugin.kt index d88f0615a9..3fbc8e009d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/dstHelper/DstHelperPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/dstHelper/DstHelperPlugin.kt @@ -21,11 +21,11 @@ import javax.inject.Singleton class DstHelperPlugin @Inject constructor( injector: HasAndroidInjector, aapsLogger: AAPSLogger, - private var rxBus: RxBus, + private val rxBus: RxBus, rh: ResourceHelper, - private var sp: SP, - private var activePlugin: ActivePlugin, - private var loopPlugin: LoopPlugin + private val sp: SP, + private val activePlugin: ActivePlugin, + private val loopPlugin: LoopPlugin ) : PluginBase(PluginDescription() .mainType(PluginType.CONSTRAINTS) .neverVisible(true) @@ -41,6 +41,7 @@ class DstHelperPlugin @Inject constructor( } //Return false if time to DST change happened in the last 3 hours. + @Suppress("ReplaceGetOrSet") override fun isLoopInvocationAllowed(value: Constraint): Constraint { val pump = activePlugin.activePump if (pump.canHandleDST()) { @@ -52,9 +53,9 @@ class DstHelperPlugin @Inject constructor( val snoozedTo: Long = sp.getLong(R.string.key_snooze_dst_in24h, 0L) if (snoozedTo == 0L || System.currentTimeMillis() > snoozedTo) { val notification = NotificationWithAction(injector, Notification.DST_IN_24H, rh.gs(R.string.dst_in_24h_warning), Notification.LOW) - notification.action(R.string.snooze, Runnable { + notification.action(R.string.snooze) { sp.putLong(R.string.key_snooze_dst_in24h, System.currentTimeMillis() + T.hours(24).msecs()) - }) + } rxBus.send(EventNewNotification(notification)) } } @@ -67,9 +68,9 @@ class DstHelperPlugin @Inject constructor( val snoozedTo: Long = sp.getLong(R.string.key_snooze_loopdisabled, 0L) if (snoozedTo == 0L || System.currentTimeMillis() > snoozedTo) { val notification = NotificationWithAction(injector, Notification.DST_LOOP_DISABLED, rh.gs(R.string.dst_loop_disabled_warning), Notification.LOW) - notification.action(R.string.snooze, Runnable { + notification.action(R.string.snooze) { sp.putLong(R.string.key_snooze_loopdisabled, System.currentTimeMillis() + T.hours(24).msecs()) - }) + } rxBus.send(EventNewNotification(notification)) } } else { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt index c0491e155b..b37f11f19f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt @@ -50,6 +50,7 @@ import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin import info.nightscout.androidaps.plugins.aps.loop.events.EventNewOpenLoopNotification import info.nightscout.androidaps.plugins.bus.RxBus import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker +import info.nightscout.androidaps.plugins.constraints.bgQualityCheck.BgQualityCheckPlugin import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus import info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity @@ -122,6 +123,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList @Inject lateinit var overviewData: OverviewData @Inject lateinit var overviewPlugin: OverviewPlugin @Inject lateinit var automationPlugin: AutomationPlugin + @Inject lateinit var bgQualityCheckPlugin: BgQualityCheckPlugin private val disposable = CompositeDisposable() @@ -661,6 +663,15 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList else binding.infoLayout.bg.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv() binding.infoLayout.timeAgo.text = dateUtil.minAgo(rh, overviewData.lastBg?.timestamp) binding.infoLayout.timeAgoShort.text = "(" + dateUtil.minAgoShort(overviewData.lastBg?.timestamp) + ")" + + val qualityIcon = bgQualityCheckPlugin.icon() + if (qualityIcon != 0) { + binding.infoLayout.bgQuality.visibility = View.VISIBLE + binding.infoLayout.bgQuality.setImageResource(qualityIcon) + } else { + binding.infoLayout.bgQuality.visibility = View.GONE + } + } OverviewData.Property.PROFILE -> { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/RandomBgPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/RandomBgPlugin.kt index b66f07ba6e..2e22db0164 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/RandomBgPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/RandomBgPlugin.kt @@ -13,11 +13,8 @@ import info.nightscout.androidaps.interfaces.PluginDescription import info.nightscout.androidaps.interfaces.PluginType import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag -import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin -import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.XDripBroadcast -import info.nightscout.androidaps.utils.buildHelper.BuildHelper import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.sharedPreferences.SP import io.reactivex.disposables.CompositeDisposable @@ -33,20 +30,18 @@ class RandomBgPlugin @Inject constructor( injector: HasAndroidInjector, rh: ResourceHelper, aapsLogger: AAPSLogger, - private val virtualPumpPlugin: VirtualPumpPlugin, - private val buildHelper: BuildHelper, private val sp: SP, - private val dateUtil: DateUtil, private val repository: AppRepository, private val xDripBroadcast: XDripBroadcast -) : PluginBase(PluginDescription() - .mainType(PluginType.BGSOURCE) - .fragmentClass(BGSourceFragment::class.java.name) - .pluginIcon(R.drawable.ic_dice) - .pluginName(R.string.randombg) - .shortName(R.string.randombg_short) - .preferencesId(R.xml.pref_bgsource) - .description(R.string.description_source_randombg), +) : PluginBase( + PluginDescription() + .mainType(PluginType.BGSOURCE) + .fragmentClass(BGSourceFragment::class.java.name) + .pluginIcon(R.drawable.ic_dice) + .pluginName(R.string.randombg) + .shortName(R.string.randombg_short) + .preferencesId(R.xml.pref_bgsource) + .description(R.string.description_source_randombg), aapsLogger, rh, injector ), BgSource { @@ -58,7 +53,7 @@ class RandomBgPlugin @Inject constructor( const val interval = 5L // minutes const val min = 70 // mgdl const val max = 190 // mgdl - const val period = 90.0 // minutes + const val period = 120.0 // minutes } init { @@ -94,15 +89,18 @@ class RandomBgPlugin @Inject constructor( } private fun handleNewData() { - if (!isEnabled(PluginType.BGSOURCE)) return + if (!isEnabled()) return val cal = GregorianCalendar() - val currentMinute = cal.get(Calendar.MINUTE) + (cal.get(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 + cal[Calendar.MILLISECOND] = 0 + cal[Calendar.SECOND] = 0 + cal[Calendar.MINUTE] -= cal[Calendar.MINUTE] % 5 val glucoseValues = mutableListOf() glucoseValues += CgmSourceTransaction.TransactionGlucoseValue( - timestamp = dateUtil.now(), + timestamp = cal.timeInMillis, value = bgMgdl, raw = 0.0, noise = null, @@ -111,11 +109,11 @@ class RandomBgPlugin @Inject constructor( ) disposable += repository.runTransactionForResult(CgmSourceTransaction(glucoseValues, emptyList(), null)) .subscribe({ savedValues -> - savedValues.inserted.forEach { - xDripBroadcast(it) - aapsLogger.debug(LTag.DATABASE, "Inserted bg $it") - } - }, { aapsLogger.error(LTag.DATABASE, "Error while saving values from Random plugin", it) } + savedValues.inserted.forEach { + xDripBroadcast(it) + aapsLogger.debug(LTag.DATABASE, "Inserted bg $it") + } + }, { aapsLogger.error(LTag.DATABASE, "Error while saving values from Random plugin", it) } ) } } diff --git a/app/src/main/res/drawable/ic_baseline_warning_24_red.xml b/app/src/main/res/drawable/ic_baseline_warning_24_red.xml new file mode 100644 index 0000000000..f01f6df79d --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_warning_24_red.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_warning_24_yellow.xml b/app/src/main/res/drawable/ic_baseline_warning_24_yellow.xml new file mode 100644 index 0000000000..2eededf7fc --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_warning_24_yellow.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/overview_info_layout.xml b/app/src/main/res/layout/overview_info_layout.xml index 7ec4d562ee..20f5e775de 100644 --- a/app/src/main/res/layout/overview_info_layout.xml +++ b/app/src/main/res/layout/overview_info_layout.xml @@ -19,6 +19,14 @@ app:layout_constraintTop_toTopOf="parent" tools:ignore="HardcodedText" /> + = ArrayList() + superData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) + superData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(15).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) + superData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(10).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) + superData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)) + autosensDataStore.bgReadings = superData + + autosensDataStore.lastUsed5minCalculation = true + plugin.processBgData() + Assert.assertEquals(BgQualityCheckPlugin.State.FIVE_MIN_DATA, plugin.state) + autosensDataStore.lastUsed5minCalculation = false + plugin.processBgData() + Assert.assertEquals(BgQualityCheckPlugin.State.RECALCULATED, plugin.state) + + val duplicatedData: MutableList = ArrayList() + duplicatedData.add( + GlucoseValue( + raw = 0.0, + noise = 0.0, + value = 100.0, + timestamp = T.mins(20).msecs(), + sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, + trendArrow = GlucoseValue.TrendArrow.FLAT + ) + ) + duplicatedData.add( + GlucoseValue( + raw = 0.0, + noise = 0.0, + value = 100.0, + timestamp = T.mins(20).msecs() + 1, + sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, + trendArrow = GlucoseValue.TrendArrow.FLAT + ) + ) + duplicatedData.add( + GlucoseValue( + raw = 0.0, + noise = 0.0, + value = 100.0, + timestamp = T.mins(10).msecs(), + sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, + trendArrow = GlucoseValue.TrendArrow.FLAT + ) + ) + duplicatedData.add( + GlucoseValue( + raw = 0.0, + noise = 0.0, + value = 100.0, + timestamp = T.mins(15).msecs(), + sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, + trendArrow = GlucoseValue.TrendArrow.FLAT + ) + ) + duplicatedData.add( + GlucoseValue( + raw = 0.0, + noise = 0.0, + value = 100.0, + timestamp = T.mins(5).msecs(), + sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, + trendArrow = GlucoseValue.TrendArrow.FLAT + ) + ) + autosensDataStore.bgReadings = duplicatedData + + autosensDataStore.lastUsed5minCalculation = true + plugin.processBgData() + Assert.assertEquals(BgQualityCheckPlugin.State.DOUBLED, plugin.state) + Assert.assertEquals(R.drawable.ic_baseline_warning_24_red, plugin.icon()) + + val identicalData: MutableList = ArrayList() + identicalData.add( + GlucoseValue( + raw = 0.0, + noise = 0.0, + value = 100.0, + timestamp = T.mins(20).msecs(), + sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, + trendArrow = GlucoseValue.TrendArrow.FLAT + ) + ) + identicalData.add( + GlucoseValue( + raw = 0.0, + noise = 0.0, + value = 100.0, + timestamp = T.mins(20).msecs(), + sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, + trendArrow = GlucoseValue.TrendArrow.FLAT + ) + ) + identicalData.add( + GlucoseValue( + raw = 0.0, + noise = 0.0, + value = 100.0, + timestamp = T.mins(10).msecs(), + sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, + trendArrow = GlucoseValue.TrendArrow.FLAT + ) + ) + identicalData.add( + GlucoseValue( + raw = 0.0, + noise = 0.0, + value = 100.0, + timestamp = T.mins(15).msecs(), + sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, + trendArrow = GlucoseValue.TrendArrow.FLAT + ) + ) + identicalData.add( + GlucoseValue( + raw = 0.0, + noise = 0.0, + value = 100.0, + timestamp = T.mins(5).msecs(), + sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, + trendArrow = GlucoseValue.TrendArrow.FLAT + ) + ) + autosensDataStore.bgReadings = identicalData + + autosensDataStore.lastUsed5minCalculation = false + plugin.processBgData() + Assert.assertEquals(BgQualityCheckPlugin.State.DOUBLED, plugin.state) + } + + @Test + fun isLoopInvocationAllowedTest() { + plugin.state = BgQualityCheckPlugin.State.UNKNOWN + Assert.assertEquals(true, plugin.isLoopInvocationAllowed(Constraint(true)).value()) + plugin.state = BgQualityCheckPlugin.State.FIVE_MIN_DATA + Assert.assertEquals(true, plugin.isLoopInvocationAllowed(Constraint(true)).value()) + plugin.state = BgQualityCheckPlugin.State.RECALCULATED + Assert.assertEquals(true, plugin.isLoopInvocationAllowed(Constraint(true)).value()) + plugin.state = BgQualityCheckPlugin.State.DOUBLED + Assert.assertEquals(false, plugin.isLoopInvocationAllowed(Constraint(true)).value()) + } + +} \ No newline at end of file diff --git a/core/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/AutosensDataStore.kt b/core/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/AutosensDataStore.kt index 30dc01477f..52d9511095 100644 --- a/core/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/AutosensDataStore.kt +++ b/core/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/AutosensDataStore.kt @@ -19,7 +19,7 @@ import kotlin.math.roundToLong class AutosensDataStore { private val dataLock = Any() - private var lastUsed5minCalculation: Boolean? = null // true if used 5min bucketed data + var lastUsed5minCalculation: Boolean? = null // true if used 5min bucketed data // we need to make sure that bucketed_data will always have the same timestamp for correct use of cached values // once referenceTime != null all bucketed data should be (x * 5min) from referenceTime